API Moddingflow

Mod Manager: установка и скачивание

1. OAuth: open https://api.moddingflow.com/oauth/authorize with PKCE, then exchange the code at https://api.moddingflow.com/oauth/token. 2. List or search: call GET https://api.moddingflow.com/v1/search?q=skyui&game=skyrim-se or GET https://api.moddingflow.com/v1/mods. 3. Install plan: call POST https://api.moddingflow.com/v1/install-plans:resolve with game_slug, game_version, and a mod, version, or artifact UUID. 4. Download resolve: call POST https://api.moddingflow.com/v1/downloads/{artifact_id}/resolve for each plan artifact. 5. Verify SHA-256: compare the completed file with hashes.sha256 or the legacy sha256 alias returned by resolve before installing. 6. Retry: on 429, 5xx, expired signed URL, or failed hash, back off, refresh the resolve response, and use fallback only when the response allows it.

Используй 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 и не требуют повторной загрузки архива.

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 для conditional requests и resume. Его значение может измениться после refresh или переключения provider, поэтому прежний ETag/If-Match нельзя переносить на fallback. Signed transport обязан сохранять stored bytes без transcoding; RFC 9530 Content-Digest и Repr-Digest текущий контракт не гарантирует, поэтому клиент опирается на response-body hash fields.

Доступ ограничен пользователем/auth context, client/app identity, scope files:download или delegated download scope, entitlement checks, 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 и соответствующим 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 и executables требуют manual 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.

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, non-null label, nullable original_filename, content_type, game_version_key и loader_key, затем size_bytes, sha256, hashes, artifact_source, status, scan_status и download_metadata. 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://api.moddingflow.com/v1. OAuth issuer URL: https://api.moddingflow.com. Website compatibility namespace: https://www.moddingflow.com/api/v1. В 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. Website compatibility namespace нужен только browser-hosted integrations на www.moddingflow.com.

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.

Desktop Mod Manager и user-created public applications открывают https://api.moddingflow.com/oauth/authorize с response_type=code, S256 code_challenge, requested scopes, state и allowlisted redirect URI. Пользователь видит приложение и запрошенные scopes и должен явно разрешить или отклонить доступ. Чувствительные scopes требуют предварительного review приложения и недавнего AAL2-подтверждения через 2FA. Authorization code обменивается в https://api.moddingflow.com/oauth/token; пароль пользователя в клиенте не хранится. Для identity assertion запроси openid со свежим nonce длиной 8-512 символов: token response вернет пятиминутный RS256 ID token, у которого нужно проверить подпись, issuer, audience равный client_id, iat/exp и nonce. Никогда не используй ID token как Bearer access token для /v1.

Refresh tokens меняются при каждом refresh. Если клиент повторно использует старый refresh token, сервер немедленно отзывает всё семейство refresh tokens и требует новую авторизацию.

CLI и fallback clients создают device grant через https://api.moddingflow.com/oauth/device_authorization, показывают user_code и verification URI, затем опрашивают https://api.moddingflow.com/oauth/token. Страница подтверждения показывает client и requested scopes и позволяет разрешить или отклонить запрос. Опрос не должен быть чаще interval; после slow_down нужно увеличить backoff, а после expired_token или access_denied остановиться.

Разработчики регистрируют OAuth applications в разделе Настройки > Для разработчиков по адресу /account/developer/applications. Новый public client получает client_id, регистрирует exact HTTPS, custom-scheme или loopback redirect URIs, выбирает поддержку Device Flow, использует PKCE S256 и может начать с basic read/install scopes без ручной модерации: mods:read, files:download, install_plans:resolve и profile:read. Каждый пользователь видит явное согласие на scopes и account-wide resource statement: одобренные OAuth permissions действуют для всех ресурсов, к которым у пользователя есть доступ. Если automation нужно ограничить выбранными модами или проектами, используйте resource-scoped PAT.

Sensitive grants требуют review перед выдачей приложению: mods:write, versions:write, files:upload, webhooks:write, private_data:read, agent:* и admin:*. Выдача чувствительных scopes также требует недавнего AAL2-подтверждения через 2FA. Unknown scopes fail closed, а admin:* никогда не доступен user-created applications. Отключение приложения немедленно применяется к authorization, token issuance, refresh и проверке access tokens.

Передавайте User-Agent в виде ApplicationName/ApplicationVersion и добавляйте в комментарий актуальный contact URL или email. PAT mutations требуют одновременно X-Moddingflow-Client и X-Moddingflow-Client-Version. В OAuth авторитетен client_id, поэтому переданный application-name header обязан совпадать с ним. Отсутствующая, generic, malformed или mismatched identity возвращает 400 invalid_client_identity до quota consumption и business side effects. Официальные TypeScript, Python, C#, CLI и GitHub Action clients передают стабильные identity headers по умолчанию.

Individual API keys и Personal Access Tokens предназначены для user-owned automation. POST /v1/api-keys и POST /v1/api-keys/{api_key_id}/rotate требуют Idempotency-Key. Тот же key и body никогда не повторяют mutation; если one-time secret уже нельзя безопасно восстановить, replay завершается fail closed с idempotency_replay_unavailable без operation-shaped committed metadata. Проверьте key через GET /v1/api-keys или revoke его до использования нового idempotency key. POST /v1/api-keys показывает raw secret ровно один раз; storage хранит только token_hash, safe fingerprint/display_prefix, scopes, resource grants, last_used_at, expires_at и rotation/revoke state. GET /v1/api-keys возвращает только безопасные metadata, а GET /v1/api-keys/capabilities показывает доступные scopes, mod projects и team role до создания ключа. Те же операции доступны автору после входа в разделе Настройки > Для разработчиков без передачи account session cookie внешним tools.

POST /v1/api-keys/{api_key_id}/rotate атомарно создает replacement с теми же scopes, resource grants и expiry и показывает новый secret один раз. Старый secret остаётся действительным только на максимальный 10-минутный overlap, после чего отзывается автоматически. DELETE /v1/api-keys/{api_key_id} выполняет немедленный idempotent revoke. Owner управляет team publishing через /v1/mods/{mod_id}/team-members: owner, maintainer, publisher и reviewer работают под собственными user identities, а audit log хранит key id/fingerprint, но никогда secret.

Запрашивай только scopes, нужные конкретной персоне: mods:read для catalog, mods:write для metadata, versions:write для version drafts, files:upload для upload, versions:publish для publish, versions:archive для archive/rollback, files:manage для file management и teams:admin для owner-only team management. files:download и install_plans:resolve нужны Mod Manager installs, webhooks:read/webhooks:write - webhook management, private_data:read - private user-owned data, agent:* - delegated tools. admin:* зарезервирован для trusted internal clients.

OAuth, PAT, upload, download и Agent Gateway calls получают rate limits до storage, signing, upload, download или tool side effects. Buckets считаются по application/client_id, user/actor_user_id и token_id_or_api_key_id, а scope и token_kind участвуют в policy matching.

При 429 clients должны учитывать Retry-After и любые RateLimit-Limit, RateLimit-Remaining и RateLimit-Reset headers. Другой PAT или OAuth app нельзя использовать для обхода user или token abuse bucket.

Runnable examples

Открой OpenAPI JSON, настрой apiBaseUrl https://api.moddingflow.com/v1 и oauthIssuerUrl https://api.moddingflow.com, затем выбери текущий job: Mod Manager install/download, creator upload/publish, catalog/search или Agent Gateway. Website compatibility namespace: https://www.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 -X POST https://api.moddingflow.com/v1/install-plans:resolve -H "Authorization: Bearer $ACCESS_TOKEN" -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({ accessToken }); 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(access_token="TOKEN"); plan = client.resolve_install_plan(body={"game_slug":"skyrim-se","game_version":"1.6.1170"}).

Creator automation начинается с POST https://api.moddingflow.com/v1/uploads, отправляет bytes напрямую в storage, обновляет multipart signed parts при необходимости, polling GET https://api.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.