1. Выполните анонимный поиск обычным curl: curl "https://moddingflow.com/v1/search?q=skyui&game=skyrim-se". 2. Получите план через POST https://moddingflow.com/v1/install-plans:resolve. 3. Для каждого артефакта вызовите POST https://moddingflow.com/v1/downloads/{artifact_id}/resolve. 4. Перед установкой проверьте hashes.sha256. Не нужны аккаунт, API key, OAuth-регистрация, Bearer-токен, специальный User-Agent или X-Moddingflow headers. URL с завершающим slash и без него обслуживаются напрямую без 308. Методы SDK TypeScript, Python и C# работают без accessToken. OAuth нужен только для вошедшего пользователя. Для 429, 5xx, истёкшего signed URL и неверного хеша используйте backoff и новый resolve.
API Moddingflow
Mod Manager: установка и скачивание
Используй POST /v1/install-plans:resolve, чтобы превратить выбранные mods, versions или artifacts в детерминированный plan. Передай game_slug, game_version, platform, loader, release_channel и один или несколько стабильных selectors. Response добавляет typed dependency_constraints, conflicts, artifact_selection, decision_reasons, stable plan_id и item_id, сохраняя legacy dependency и step fields. Required и optional dependencies, conflicts и embedded contents машиночитаемы; embedded content не создаёт отдельный download, а optional artifact требует include_optional=true. Новые клиенты считают hashes.sha256 canonical и могут читать sha256 как compatibility alias; оба поля покрывают полные неизменяемые байты сохранённого объекта и проверяются после сравнения size_bytes. Затем вызови POST /v1/downloads/{artifact_id}/resolve для каждого artifact и устанавливай файл только после успешной SHA-256 проверки полного архива. Старые публичные file URLs во время миграции сохраняют ту же artifact identity и не требуют повторной загрузки архива.
Ручная проверка безопасности не добавляет public field или новый error code в v1. Artifact со статусом «На проверке» разрешается обычным resolve-потоком; небезопасный artifact fail-closed возвращается через уже документированный generic 404/unavailable contract и не выдаёт signed URL. Клиент не должен пытаться отличить этот случай от другой недоступности artifact.
Успешный POST https://moddingflow.com/v1/downloads/{artifact_id}/resolve возвращает тот же полный mod: PublicMod, что GET-каталог, точную выбранную version и скачиваемый artifact, а также distribution.service=moddingflow с постоянными website_url, mod_url и api_url. Выбранная version может отличаться от mod.latest_release — клиент не должен заменять её последней. primary_url и expires_at описывают временную прямую подписанную ссылку; после истечения срока повтори stable download_grant.resolve_endpoint (он также указан в resume.signed_url_expiry_refresh). Не сохраняй primary_url и не подменяй ею постоянные links.web/source_url/homepage_url.
Download resolve создаёт или возобновляет привязанный к actor долговечный download job с одним стабильным короткоживущим grant и после успешной активации grant отвечает 200 JSON; он не использует 302 redirect и не стримит binary body. Сохраняй artifact_id, download_job.id, download_job.grant_id и download_session_id, а не primary_url, head_url или fallback_url. primary_url предназначен для GET с Range и условными заголовками If-Match/If-None-Match/If-Modified-Since/If-Unmodified-Since, а отдельный head_url — для HEAD с Content-Length и ETag. Используй expires_at, expires_in и refresh_after_seconds, чтобы обновить grant через POST /v1/downloads/{artifact_id}/resolve, сверить те же hashes.sha256/sha256 и size_bytes и продолжить с последнего durable byte. Fallback вызывает POST /v1/downloads/{artifact_id}/fallback с downloadSessionId и выдаёт новую signed URL только пока grant активен.
hashes.sha256 и legacy alias sha256 — application-level SHA-256 полных неизменяемых байтов сохранённого объекта. После обычного HTTP decoding нужно собрать полный объект из всех Range частей в правильном порядке, сверить size_bytes и только затем проверить digest; отдельная 206 часть этим full-file hash не проверяется. ETag не является криптографическим digest: это opaque validator одной transport representation. Читай representation.provider, etag_scope, etag и if_match и отправляй If-Match только когда representation.provider совпадает с etag_scope. После provider failover fallback намеренно возвращает etag=null, if_match=null и requires_head_before_range=true: сначала выполни HEAD fallback representation и используй только новый ETag этого provider. Signed transport обязан сохранять stored bytes без transcoding; RFC 9530 Content-Digest и Repr-Digest текущий контракт не гарантирует, поэтому клиент опирается на response-body hash fields.
Перед URL signing access tier проверяется fail-closed: public разрешает anonymous download, authenticated требует actor, paid требует active subscriber entitlement, а restricted — active artifact/mod resource grant; expired или revoked entitlement отвечает 403 без signed URL. Доступ также ограничен client/app identity, scope files:download или delegated download scope, route rate limits и byte budgets. 404 означает недоступный artifact, 401/403 — проблему auth или entitlement, 429 — backoff, а 410 или download_session_expired — истёкший или непригодный grant. При 429 соблюдай Retry-After, сохраняй безопасный download_job из Problem Details и повторяй запрос с job_id, монотонным progress.bytes_received, progress.provider и соответствующим ETag; повторные лимиты сохраняют те же job и grant без повторного provider work. Hosts, provider names и query strings у signed URL являются opaque transport details и могут меняться без breaking changes для стабильных клиентов.
Считай 400 и 422 ошибками запроса, 401 или 403 проблемами аутентификации или scope, 404 или 410 устаревшим выбором, 409 конфликтом для повторного планирования, 429 сигналом паузы, а 5xx повторяемыми с jitter. Ошибки используют RFC 9457 Problem Details с application/problem+json: ветвись по type или code, а не по локализованному detail. Каждая runtime-ошибка содержит retryable и docs_slug. retry_after_seconds и quota_bucket появляются только при безопасной подсказке о повторе или квоте, required_scopes только когда эти scope действительно разрешат запрос, а fields только для безопасных ошибок валидации. Registry x-moddingflow-error-registry в OpenAPI является источником истины и развивается только additively внутри v1; сохраняй неизвестные будущие коды и используй числовой status как fallback. Типизированные ошибки SDK сохраняют эти поля в разобранном payload вместе с headers, request_id и trace_id. На 429 жди минимум Retry-After секунд и читай RateLimit-Limit, RateLimit-Remaining и RateLimit-Reset перед экспоненциальной задержкой с jitter. Держи один Idempotency-Key на логическую запись или детерминированный повтор resolve; 409 idempotency_conflict означает, что нужно повторить исходное тело запроса или начать новую операцию с новым ключом. Всегда сохраняй X-Request-Id, request_id и trace_id для журналов поддержки.
Creator upload и publish automation
Основной Upload API target: автор собирает mod archive локально или в CI и публикует new version для existing mod без browser form. Тот же path должен подходить для GitHub release или tag pipeline: build archive, run local checks, создать или переиспользовать version draft, upload direct-to-storage, дождаться scan и validation, затем publish только после статуса ready.
Team publishing использует individual API keys, OAuth apps или PATs с минимальными доступными scopes, чтобы maintainers не делились owner password или main account session. Tool integrations должны уметь показывать кнопку Publish to Moddingflow в launcher, editor или build tool и при этом отдавать тот же machine-readable upload status, request_id и trace_id, который видит CI.
Failure states считаются частью product contract: upload_interrupted оставляет upload resumable или abortable, scan_failed и validation_failed блокируют publish с деталями, permission_denied называет missing scope или project access, quota_exceeded возвращает retry guidance, а duplicate_version просит переиспользовать или изменить version. Подозрительные файлы уходят в quarantine/review; clients получают clear status вместо silent failure. Public promise: automation безопаснее manual form благодаря direct cloud upload, least-privilege keys, no shared owner credentials, auditable provenance и security checks до того, как файл попадет к users.
Сначала вызови validate-only endpoint POST /v1/uploads:validate: он проверяет sizeBytes, expectedHashes.sha256, archiveManifest и buildMetadata без создания session, storage object или audit write. Затем automation автора открывает POST /v1/uploads, отправляет archive bytes напрямую в private Cloudflare R2 через signed single PUT URL или signed multipart parts, обновляет parts через POST /v1/uploads/{upload_id}/parts, проверяет GET /v1/uploads/{upload_id}, завершает POST /v1/uploads/{upload_id}/complete и отменяет POST /v1/uploads/{upload_id}/abort. Next.js API получает только metadata и lifecycle calls, а не body архива. Большая multipart session активна 48 часов, каждая signed URL - 15 минут. status.progress возвращает uploadedBytes, percent, totalParts и подтвержденные provider completedParts с ETags, поэтому Website, CLI и GitHub Action продолжают после обрыва только missing parts. Private staging object нельзя publish до complete, проверки размера/content type/archive format и security processing; bounded cleanup job abort-ит expired multipart upload или удаляет incomplete single object. Complete возвращает async job, пока scan, metadata extraction, indexing hooks или final promotion идут в фоне; опрашивай job.statusEndpoint. queued и processing являются non-terminal, а succeeded, rejected, failed и cancelled - terminal. job.progress честно показывает completion 0/1, а не выдуманный scan percentage. createdAt, updatedAt, startedAt, completedAt, retry attempts, nextAttemptAt и стабильный terminal reason позволяют отличить active, retrying и finished work без раскрытия provider prose. Канонический lifecycleStatus: draft, uploading, uploaded, validating, scanning, quarantined, ready, published, failed, cancelled; legacy status остается compatibility alias. expectedSha256 поддерживается как legacy alias, sha256_mismatch отклоняет upload до promotion. Idempotency-Key должен оставаться одинаковым для create и complete retries. Version request metadata включает version, changelog, file_category, game_slug, game_versions, loader_platforms, dependencies, requirements и build_metadata; повтор логической версии возвращает duplicate_version. metadata-only edits не требуют повторного upload bytes. Security worker атомарно claim-ит durable jobs, повторяет transient failures и переводит exhausted/unverified uploads в quarantine. ZIP policy возвращает archive_dangerous_entry, archive_encrypted_entry, archive_symlink_entry, archive_duplicate_path или archive_path_traversal; nested archives рекурсивно проверяются до глубины 3; запрещённые executable-сценарии считаются unsafe, а любой файл внутри архива выше лимита ClamAV требует manual review.
Изолированный archive-security-worker проверяет immutable scan seal: полный SHA-256, структуру ZIP/7z/RAR и ClamAV-вердикт. Exact-SHA clean cache переиспользуется только при совпадении формата, версии политики, движка и базы сигнатур. Временные сбои получают не более трёх попыток; unknown, устаревшая база и превышение ресурсов никогда не становятся clean и оставляют файл закрытым в quarantine/review.
Числовые upload defaults: single PUT до 100 MiB; multipart part 16 MiB с concurrency 4; один signing request содержит максимум 4 parts, поэтому payload in flight ограничен 64 MiB; один archive — максимум 20 GiB. Authenticated upload byte bucket — 24 GiB. Live R2 capacity probe подтвердил 64 MiB upload/download с SHA-256 equality, RSS delta ниже 384 MiB и обязательным cleanup; профиль 64 MiB × 4 был отклонён как превышающий memory budget.
Если complete создаёт non-terminal job со статусом queued или processing, HTTP-ответ имеет 202 Accepted, а заголовок Location в точности равен job.statusEndpoint. Если job уже terminal (succeeded, rejected, failed или cancelled), включая идемпотентный replay terminal результата, complete возвращает 200 OK без повторного side effect.
Повтор POST /v1/uploads с тем же body и Idempotency-Key возвращает исходную durable session с заново подписанными upload-инструкциями. Каждый single PUT URL имеет максимум 900-second lease, который сервер row-lock checkpoint-ит после sign и до response; abort-winning checkpoint отклоняет выдачу URL. Отправляй все возвращённые headers, включая If-None-Match: *. conditional 412 означает ambiguous existing write: не перезаписывай staging, а вызови тот же complete endpoint, где HEAD, immutable seal и SHA-256 являются authoritative. Signed URL и provider upload ID никогда не сохраняются в generic idempotency record; после неоднозначного create timeout сначала проверь upload-session и только потом начинай новую mutation.
Для polling guidance используйте retry.retryable, retry.strategy, retry.attempt, retry.maxAttempts, retry.nextAttemptAt и retry.retryAfterSeconds. HTTP response может передать ту же минимальную задержку через Retry-After.
Ручная форма загрузки мода на сайте использует тот же upload-session и закрытый R2 backend. Она показывает передачу файла и проверку безопасности, сохраняет созданный черновик мода после сетевого обрыва и повторно использует подтверждённые provider multipart-части при выборе того же файла. Перед multipart commit сервер повторно сверяет точный размер и provider ETag/MD5 каждой части, а асинхронный trust worker потоково проверяет SHA-256 всего объекта до ready/publish. В разделе Настройки > Для разработчиков видны последние sessions, ошибки scan и статус quarantine/review.
Выбирайте manual browser form для редкого релиза с пошаговым интерфейсом. Используйте Upload API для повторяемых local tools, CI и GitHub Actions. Оба пути используют same upload-session backend, private Cloudflare R2 storage, validation, scan, quarantine/review и ready-before-publish security gates.
Выдавайте каждому человеку или automation individual resource-scoped key с smallest required scopes. raw secret показывается one-time. Храните MODDINGFLOW_API_KEY в GitHub environment or repository secret, никогда не помещайте его в workflow YAML, repository variables, logs, command arguments или artifacts. Используйте protected environments, minimal workflow permissions и проверенный 40-character Action commit SHA.
После scan_failed или av_scan_rejected не публикуйте и не перепаковывайте архив для обхода verdict. Оставьте файл private, опрашивайте GET /v1/uploads/{upload_id}, сохраните supportCorrelationId или X-Upload-Correlation-Id, request_id, trace_id и error code, затем следуйте quarantine/review, manual review или support guidance. Correlation id можно отправить support: он не содержит secret и не даёт доступ. Создавайте new session только после исправления найденной причины.
Откройте FAQ для выбора workflow и API reference для схем статусов, ошибок и scopes.
Game, Mod, ModVersion, Artifact, Blob, UploadSession, DownloadGrant, User, Team, Dependency, Job, WebhookSubscription и WebhookDelivery имеют отдельные opaque UUID, ownership rule и fail-closed state machine. Slug, username, team name, filename, URL, storage key, ETag и hash не являются public identity. Сохраняйте UUID из ответа; rename не меняет его.
Archive является reversible, но permanent delete создает retained tombstone. Неизвестный UUID возвращает 404, tombstoned UUID возвращает 410 и никогда не переиспользуется. Canonical version lifecycle: draft → validating → scanning → ready → published → archived/withdrawn; failed является явным исходом. uploaded, quarantined, approved и rejected остаются compatibility aliases для старых клиентов.
При первой публикации сервер фиксирует immutable snapshot version/file kind/release channel/game and loader bindings/Blob UUID/size/content type и его SHA-256. После публикации эти поля нельзя менять in-place; archive/withdraw restore допустим только при точном совпадении snapshot и повторном прохождении текущих upload-trust gates. Для публичных concurrent Mod/ModVersion PATCH используйте strong ETag в If-Match: missing validator возвращает 428, stale validator — 412 и текущий ETag. lifecycle_version остаётся внутренним optimistic lock lifecycle transition.
Publishing automation создает или обновляет mod draft, создает version draft, прикрепляет clean upload artifact, валидирует metadata и вызывает publish только когда автор готов. Для mod metadata нужен mods:write, для version changes нужен versions:write.
Интеграции каталога и поиска
Для catalog integrations используй GET /v1/games, GET /v1/categories, GET /v1/mods, GET /v1/mods/{mod_id}, GET /v1/mods/{mod_id}/versions, GET /v1/mods/{mod_id}/dependencies и GET /v1/search. List и search используют cursor-first keyset pagination с cursor, limit (default 25, max 100), stable tie-breaking и opaque v1 base64url cursor values; передавай cursor из предыдущего ответа и не создавай offset или page-number cursors. Каждый cursor привязан к resource, normalized filters, sort и insertion snapshot первой страницы. Повторное использование с изменённым контекстом возвращает 400 invalid_input, а строки, созданные или опубликованные после первой страницы, не входят в snapshot этого cursor. GET /v1/mods и GET /v1/search принимают filters game_slug, q, sort=relevance|latest|trending|downloads, game_version, loader и category; website compatibility namespace также принимает aliases game/gameSlug, query/search, gameVersion/version, platform и facet. Search требует q и по умолчанию использует sort=relevance, а list без query по умолчанию сортируется как latest. Reserved resource filters: versions: mod_id, release_channel, game_version, loader, published_after, published_before; files: mod_id, version_id, artifact_id, file_kind, status, sha256; upload sessions: status, strategy, file_id, created_after, created_before; download grants: artifact_id, status, expires_after, expires_before; webhook deliveries: event_kind, delivery_status, created_after, next_attempt_before.
Anonymous catalog GET responses используют Cache-Control public, weak ETag и Vary: Accept-Encoding, Authorization, Cookie. Передай сохранённый ETag в If-None-Match, чтобы получить пустой 304, если representation не изменилась. Request с Authorization или Cookie всегда получает private no-store и не использует public validator, поэтому authenticated и anonymous responses не разделяют один cache entry.
Пример получения полного снимка: GET https://moddingflow.com/v1/mods?game_slug=skyrim-se-ae&limit=25, затем GET по абсолютному links.api. PublicMod сохраняет summary как краткий excerpt, а description возвращает полный Markdown первого видимого сообщения без обрезки и с description_format=markdown. author содержит только Moddingflow uploader; profile_url равен null у закрытого профиля. latest_version остаётся compatibility field и вычисляется из первого активного основного файла, а latest_release содержит авторитетные id, такую же файловую version, primary_artifact_id, first-publication published_at и release updated_at либо null для legacy-записи без подтверждённой версии. links.web — постоянная страница Moddingflow, links.api и links.versions — постоянные API URL; homepage_url относится к сайту автора или проекта, а source_url остаётся указанной uploader исходной upstream-страницей и не подменяется страницей Moddingflow.
Public payloads используют стабильные UUID для games, mods, versions, artifacts и dependencies. Не привязывай интеграцию к website forum topic IDs, storage object names или human-readable slugs как durable keys.
Переименования, metadata rebuilds и slug changes сохраняют существующие mod, version, artifact, upload-session и download-grant identifiers. Замена байтов файла создает новый physical blob и может создать новую logical artifact/version binding; metadata-only edits не заставляют клиентов повторно загружать файлы.
Стабильная модель v1 разделяет mod, version, logical file, physical blob, upload-session, download-grant, token и webhook-delivery. Mod владеет versions, version указывает на logical files через artifact ids, а logical file может быть связан с неизменяемым physical blob в storage без раскрытия storage paths.
Считай slug и localized slugs изменяемыми aliases, filename - display/download текстом, а hash values вроде sha256 или hashes.sha256 - проверками целостности. Сохраняй public ids и lifecycle ids: mod id, version id, artifact id, sessionId, download_session_id, token metadata id и webhook delivery id после публикации webhook delivery endpoints.
PublicModVersion.artifact_ids остаётся стабильной связью. Optional PublicModVersion.artifacts появляется только для managed primary artifact, когда size_bytes > 0, sha256/hashes.sha256 точны, current upload session совпадает по file_id и final_blob_id, actual AV scan_status=clean, lifecycle=ready, publication state=published и managed resolve доступен; иначе expansion omitted. Полный объект содержит id, mod_id, version_id, file_kind, file_version конкретного файла, non-null label, nullable original_filename, content_type, game_version_key и loader_key, затем size_bytes, sha256, hashes, artifact_source, status, scan_status и download_metadata. PublicModVersion.version равна artifact.file_version; PublicMod.latest_version и latest_release.version отражают версию первого активного основного файла для совместимости. Actual AV states: pending, queued, scanning, clean, quarantined, rejected, failed, skipped. download_metadata содержит relative resolve_endpoint и truthful range_supported, но не provider URL.
Agent Gateway delegated tools
Agent Gateway предоставляет узкие tools для mods.search, mods.get, install_plans.resolve, downloads.resolve, versions.compare, publisher.draft и publisher.validate. Агентам нужно вызывать tools, а не парсить страницы и не fetch arbitrary URLs.
Agent получает короткоживущий token с audience agent-gateway и узкими tool scopes. Он никогда не получает полный user session token. Read tools можно запускать напрямую; write-capable tools остаются dry-run, пока confirmation token не разрешит конкретное действие.
Auth и границы URL
API base URL: https://moddingflow.com/v1. OAuth issuer URL: https://moddingflow.com. API compatibility namespace: https://moddingflow.com/api/v1. Оба namespace принимают operation URL с завершающим slash и без него напрямую, без 308. В official CLI есть local sandbox profile http://127.0.0.1:3000/api/v1; protected staging использует выданный оператором MODDINGFLOW_STAGING_API_BASE_URL и отдельный staging key, а не анонимный общий credential.
API base URL используется для /v1 resources. OAuth issuer URL используется для /oauth/authorize, /oauth/token, /oauth/device_authorization, /oauth/revoke, /oauth/introspect и RFC 8414 metadata по /.well-known/oauth-authorization-server. OIDC-клиенты используют /.well-known/openid-configuration и /.well-known/jwks.json; ответ 503 означает OAuth-only deployment, которому нельзя отправлять scope openid. Канонический transport остаётся на apex https://moddingflow.com/v1; /api/v1 — совместимый alias. Отдельный API-host не требуется.
Stable v1 использует REST/JSON over HTTPS. Read endpoints остаются GET resource routes, включая GET /v1/uploads/{upload_id} для upload-session status, а write endpoints остаются POST/PATCH mutations и lifecycle actions используют явные action routes вроде /v1/uploads/{upload_id}/complete, /v1/mods/{mod_id}/versions/{version_id}/publish и /v1/install-plans:resolve. Public clients адресуют файлы через artifact_id, upload_id и download_session_id, а не через storage object keys, provider bucket names или signed URL secrets.
Интеграция native, desktop, mobile и CLI-приложений через Authorization Code с обязательным PKCE S256, Device Flow и регистрацию без секрета. OAuth
Runnable examples
Открой OpenAPI JSON, настрой apiBaseUrl https://moddingflow.com/v1 и oauthIssuerUrl https://moddingflow.com, затем выбери текущий job: Mod Manager install/download, creator upload/publish, catalog/search или Agent Gateway. API compatibility namespace: https://moddingflow.com/api/v1. Для creator automation команда npm run api:cli -- auth check проверяет credential окружения, а npm run api:cli -- publish --mod-id <id> --archive <path> --version <version> выполняет validation, direct upload, ожидание scan и publish. Отдельные upload create, resume, complete и status используют явно заданный защищенный state file.
Анонимный curl без API key, Bearer и специальных headers: curl -X POST https://moddingflow.com/v1/install-plans:resolve -H "Content-Type: application/json" -d "{\"game_slug\":\"skyrim-se\",\"game_version\":\"1.6.1170\",\"mod_ids\":[\"11111111-1111-4111-8111-111111111111\"],\"include_optional\":false}". TypeScript: const client = new ModdingflowPublicApiClient(); await client.resolveInstallPlan({ body: { game_slug: "skyrim-se", game_version: "1.6.1170", mod_ids: [modId] } }); C#: var plan = await client.ResolveInstallPlanAsync(new ResolveInstallPlanRequest { GameSlug = "skyrim-se", GameVersion = "1.6.1170" }); Python: client = ModdingflowPublicApiClient(); plan = client.resolve_install_plan(body={"game_slug":"skyrim-se","game_version":"1.6.1170"}).
Creator automation начинается с POST https://moddingflow.com/v1/uploads, отправляет bytes напрямую в storage, обновляет multipart signed parts при необходимости, polling GET https://moddingflow.com/v1/uploads/{upload_id}, completes upload, ждет ready или failed, затем создает version draft и публикует. Используй один Idempotency-Key на логическую create/update операцию, чтобы retries не создавали duplicate drafts.
Reference, SDK и package policy
Используй API reference page для endpoint list, request/response schemas, scopes, servers, deprecation metadata и examples, сгенерированных из OpenAPI.
До публикации packages SDK считается repo-only source artifact. Открой SDK source page со ссылками на source archive и snippets подключения, затем используй repo/local imports: TypeScript imports sdk/typescript/src/moddingflow-public-api.ts, C# references sdk/csharp/Moddingflow.PublicApi, а Python добавляет sdk/python в PYTHONPATH перед import sdk/python/moddingflow_public_api. SDK source пока not published to npm, PyPI, or NuGet. Локализованные текстовые поля title, summary и description остаются language-code maps: TypeScript exposes [key: string]: string, Python exposes dict[str, str], and C# exposes Dictionary<string, string>. Python request DTOs сохраняют OpenAPI required fields через Required[...] и NotRequired[...], поэтому type checker ловит install-plan body без game_slug или game_version. Generated clients по умолчанию выполняют не больше трёх попыток и повторяют только безопасные/idempotent методы или записи с Idempotency-Key. Они учитывают Retry-After и RateLimit-Reset с ограниченным exponential backoff и jitter, сохраняют финальные request/trace IDs и могут обновить истёкший signed download URL через явный resolve hook. Regenerate and verify local artifacts with npm run api:sdks:generate и npm run api:sdks:check. Release gate также запускает npm run api:sdks:compile: локально пакует и устанавливает npm, wheel и NuGet artifacts в реальные consumers и проверяет CLI/GitHub Action без публикации registry packages.
Support/EOL matrix в docs/public-api/SDK-SUPPORT-AND-EOL.md хранит версии каждого SDK, CLI и Action отдельно от contract release 1.0.0-foundation, а также runtime floors и distribution state. Текущие artifacts имеют Preview source status без registry availability, SLA, signature или stable EOL promise. Stable support window начинается только после clean immutable publication и обновления matrix датированными обязательствами.
Backwards compatibility policy: stable v1 old clients продолжают работать весь support window. Public operationIds, fields, scopes, error codes, headers, cursor semantics, idempotency и lifecycle behavior нельзя удалить, переименовать или изменить на incompatible type без новой major version или завершенного deprecation path. Compatible additive fields могут выходить в текущем v1 contract, если old clients могут их игнорировать; new request fields должны быть optional, nullable или иметь defaults. New endpoints можно добавлять в v1, если они не заменяют существующий stable flow и не становятся скрытым prerequisite. New enum/status values требуют tolerant client guidance, docs и SDK updates. Beta или experimental endpoints должны быть marked with x-moddingflow-stage=beta, x-moddingflow-stage=experimental или жить в future /v1/beta/* path, и они не должны быть required for stable flows. Deprecated operations и HTTP-visible capabilities используют Deprecation response header как RFC 9745 Structured Field date, Sunset как RFC 8594 HTTP-date и Link rel="deprecation" для migration guide. Breaking removals требуют at least 90 days warning или two stable API releases, migration guide с replacement, detection, rollout, deadline и support contact, а также dedicated changelog entry. Any deprecated OpenAPI operation must include x-deprecation-date, x-sunset-date, x-migration-url, and x-changelog-url; OpenAPI lint gate отклоняет неполную deprecation metadata. Repository release gate также отклоняет Sunset, пока exact dates не привязаны к committed migration, ready replacement, dated telemetry и approved support-owner evidence.
Release notes, migration guide templates и changelog evidence для stable v1 собраны на странице API changelog.
Webhook automation
Используй POST /v1/webhook-subscriptions со scope webhooks:write для owner-scoped subscription на mod.updated, version.published, file.ready, file.failed, upload.completed, scan.completed, token.changed или app.changed. Target должен использовать публичный HTTPS на порту 443. API один раз возвращает Vault-backed whsec_ signing_secret; POST /v1/webhook-subscriptions/{subscription_id}/rotate-secret выдаёт новую версию с ограниченным overlap window. Проверяй X-Moddingflow-Signature как HMAC-SHA256 over `${timestamp}.${raw_body}` с X-Moddingflow-Timestamp и отклоняй stale timestamps. Versioned body содержит spec_version, стабильный event_id, delivery_id, type, version, occurred_at, opaque subject IDs и data. Delivery работает at-least-once и без гарантии порядка: быстро подтверждай, обрабатывай асинхронно, дедуплицируй по event_id и получай актуальное состояние. Automatic retries используют exponential backoff и завершаются exhausted. Manual redelivery через POST /v1/webhook-deliveries/{delivery_id}/redeliver с Idempotency-Key создаёт новый delivery_id со связью с исходным, сохраняя event_id. Repeated terminal failures могут disable the subscription. GET /v1/webhook-deliveries со scope webhooks:read показывает delivery_status, attempt_count, next_attempt_at, last_response_status, latency, очищенный response snippet, retry state и связи.
Для trust-переходов загрузки scan.completed фиксирует неизменный результат сканирования, включая quarantine. Последующее ручное одобрение создаёт отдельное событие file.ready: ни одно логическое событие не перезаписывается, и получатель может дедуплицировать их независимо.
Operations, limits и support
Успешный version mutation response — authoritative committed результат этой операции и всегда private, no-store. Anonymous catalog/version GET revalidate-ит shared cache, сохраняет weak representation ETag и использует public, max-age=0, s-maxage=0, no-cache, must-revalidate. Catalog freshness SLO — p95 не более 15 секунд, search-index freshness — p95 не более 1800 секунд; dashboard использует собственное окно каждой цели и учитывает возраст unfinished jobs, поэтому может честно оставаться critical после recovery. Не считай появление версии в каталоге подтверждением commit и не удаляй historical evidence ради green SLO. Mod/ModVersion PATCH требует один strong quoted If-Match из create, authenticated detail или предыдущего PATCH: missing возвращает 428 precondition_required; malformed/weak/wildcard/list — 412 precondition_failed без mutation; синтаксически корректный stale validator — 412 с текущим ETag. После 412 fetch detail, rebase change и используй новый ETag вместе с новым Idempotency-Key; blind retry запрещён.
Cached PublicModVersion artifact expansion требует actual AV scan_status=clean, lifecycle=ready и publication state=published и остаётся eventual. Direct download resolve и install-plan resolve — private point-in-time reads по eligible target, чьё established DB state может быть approved или published. Direct eligibility не является published-only или legacy-only; запроси resolve снова, если могли измениться version, artifact, dependency, entitlement или publication state.
GET /v1/uploads/{upload_id} возвращает current DB state на момент запроса, а worker transitions происходят eventual. Poll upload status и job.statusEndpoint до terminal state. Provider progress authoritative только при progress.available=true; progress.available=false означает unknown, а не zero bytes и не потерю принятых multipart parts.
Install plan — deterministic private/no-store request-time snapshot с ETag, а не live subscription. Resolve it again после более позднего изменения version, artifact, dependency, entitlement или publication.
Webhooks feature-gated. Когда management и delivery включены, доставка eventual, at-least-once, replayable и unordered: быстро верни 2xx, deduplicate по event_id, допускай replay/reordering и fetch latest state. Webhook arrival не подтверждает mutation; пока хотя бы один gate выключен, no delivery is promised.
Конвейер работает fail closed и не заявляет cross-provider transaction. Create сначала фиксирует once-only byte-budget checkpoint для одной generation request hash, затем до provider work атомарно резервирует session/file intent вместе с audit upload.accepted. Lost response между этими checkpoint повторно использует тот же budget result, поэтому exact replay не списывает budget и не пишет audit дважды. Каждый single PUT подписан с If-None-Match: *; его максимум 900-second URL lease row-lock checkpoint-ится после sign и до response, а abort-winning checkpoint отклоняет URL. Conditional 412 считается ambiguous existing write: client вызывает complete, где server HEAD, immutable seal и SHA-256 authoritative. После provider completion единый session-level claim условно копирует точный client-source ETag в write-once server-only seal. Seal key сохраняется до CopyObject, lost copy response восстанавливается точной HEAD-проверкой, а конкурирующие completion keys не могут перезаписать связанный seal. Immutable identity попадает в queued scan checkpoint с pending SHA; fenced worker потоково вычисляет и атомарно attests полный hash до validation, AV или final copy. После lost response повтори тот же нормализованный запрос с тем же Idempotency-Key; после checkpoint и job достаточно опрашивать статус.
Content-addressed final copy ещё не ready. Same-SHA promotion сериализуется durable SHA/bucket/key claim: winner владеет canonical MIME и использует Cloudflare header cf-copy-destination-if-none-match: *, а fresh duplicate ждёт или принимает exact ready blob без расхода normal scan attempt. Contention polling экспоненциально растёт от 1 до 30 секунд и ограничен 15 минутами на claim. Первые два durable token-fenced yield атомарно восстанавливают normal attempt и requeue-ят job с вычисленным в DB jitter 30–60 секунд; третий атомарно создаёт manual review/quarantine вместо бесконечного retry. Lost-response replay возвращает тот же persisted queued либо quarantined outcome. Lost copy response принимается только после HEAD-проверки SHA metadata, size, canonical MIME и nonblank provider ETag. Сначала должен существовать accessible attested blob, затем atomic ready binding связывает immutable blob, logical file и upload session вместе с audit и event file.ready. Во время ручной проверки quarantine сохраняется до provider attestation: одобрение атомарно фиксирует ready binding, решение review, decision audit и event, а отклонение — rejected-состояния session/file/review, decision audit и event. Transactional publish и durable outbox commit выполняются в одной database transaction; до ready binding metadata нельзя публиковать.
Heartbeat-backed claim token ограждает каждую scan mutation: stale worker после reclaim не может изменить retry, clean, quarantine, rejection, audit или event. Каждый новый переход ready, quarantine или rejection фиксирует session, file, job, audit и deterministic canonical event в одной database transaction. Exact replay проверяет terminal envelope; после неоднозначного RPC-ответа worker принимает только точно перечитанный envelope. Bounded reconciliation исправляет только явные legacy gaps, соблюдая ownership active claims. Deployed webhook delivery не обещается. Account-inbox notification не является lifecycle acknowledgement; authoritative остаётся machine-readable upload status.
Перед provider config и normal scan claim bounded DB-only sweep восстанавливает stale max-attempt processing claim без повторного storage или scan work. Coherent state атомарно получает canonical manual review/quarantine; incoherent state terminal-fail-ит только exact token-fenced job с manual_intervention_required, поэтому проблема видна без выдуманного session/file state. Одна плохая строка не блокирует независимые job, а recovery расходует тот же bounded worker batch budget.
Recovery сначала повторяет доказанный seal, content-addressed copy или binding. Cleanup имеет отдельный claim token. Для single staging durable stage 1 условно пишет zero-byte tombstone и provider-проверяет ETag, LastModified, metadata token и size=0; tombstone удерживается до persisted latest URL lease и минимум 16 минут. Durable stage 2 выполняет exact delete, HEAD-проверку отсутствия и DB completion; crash replay продолжает текущий stage. Multipart cleanup abort-ит и повторно перечисляет все exact-key uploads, а каждый seal claim key удаляется и HEAD-проверяется; expired uploaded sessions без checkpoint тоже включены, quarantine сохраняет seal. Generation byte-budget reservations удаляются только после replay window с запасом. Desired CORS policy включает If-None-Match, а object-level probe подтверждает conditional copy и late-PUT tombstone rejection, но live CORS/lifecycle/lock evidence остаётся BLOCKED до operator inputs CLOUDFLARE_API_TOKEN, MODDINGFLOW_R2_FINAL_LOCK_MIN_SECONDS и MODDINGFLOW_R2_ALLOWED_BROWSER_ORIGINS; live deployment не заявляется. Unbound final copy remains non-ready and non-publishable, is never publishable и остаётся под retention/lock до reference-aware deletion из PR-F3.13. Numeric freshness остаётся PR-F2.6.3, deployed webhook enablement — PR-F8.14.
API применяет route, scope, body-size, byte-budget, download concurrency и abuse limits до дорогих side effects. Download leases: 2 anonymous или 4 authenticated на actor, 16 на OAuth/PAT client, expiry 300 секунд; byte buckets — 24 GiB anonymous, 40 GiB authenticated и 24 GiB upload. Multipart default — 16 MiB × 4, максимум четыре signed parts за request и 64 MiB payload in flight. Oversized bodies возвращают 413. Overloaded или abusive clients получают 429 с Retry-After и RateLimit headers, а также retryable, retry_after_seconds и безопасным quota_bucket. Каждая application/problem+json ошибка также содержит code, docs_slug, request_id, trace_id и X-Request-Id; scope и validation errors добавляют required_scopes или fields только когда эти данные помогают исправить запрос. Error codes развиваются additively внутри v1, поэтому client должен сохранять неизвестный code и использовать числовой HTTP status как fallback.
Перед использованием нового API artifact проверь, что OpenAPI, SDK generation, compatibility checks, localized docs и reference rendering прошли для release. Stable v1 также требует contract_test_matrix coverage: DTO/schema, Problem Details, cursor pagination, upload-session lifecycle, download-grant lifecycle, OAuth/PAT scopes, webhook signature, webhook retry/redelivery, backwards compatibility и client flow smoke. Дополнительно требуется final_readiness_checklist coverage: public endpoints documented, opaque ID entity addressing, signed URL/resumable upload, download-grant downloads, multiple hash algorithms, async status/job endpoints, OAuth/PAT self-service, Problem Details, cursor-based pagination, webhook signatures and redelivery, Deprecation/Sunset policy, current OpenAPI spec, current FAQ and public documentation, passed tests and validations, known limitations list и first stable release notes. Если endpoint отсутствует в OpenAPI, считай его website-internal, а не public developer surface.