Moddingflow API

Mod Manager Installation und Download

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.

Nutze POST /v1/install-plans:resolve, um ausgewählte mods, versions oder artifacts in einen deterministischen plan umzuwandeln. Sende game_slug, game_version, platform, loader, release_channel und mindestens einen stabilen selector. Die Response ergänzt typed dependency_constraints, conflicts, artifact_selection, decision_reasons, stabile plan_id und item_id Werte und behält legacy dependency und step fields. Required und optional dependencies, conflicts und embedded contents sind maschinenlesbar; embedded content erzeugt keinen separaten download, optionale artifacts benötigen include_optional=true. Neue Clients behandeln hashes.sha256 als canonical und können sha256 als compatibility alias lesen; beide Felder decken die vollständigen unveränderlichen gespeicherten Objektbytes ab und werden nach dem Vergleich von size_bytes geprüft. Rufe danach POST /v1/downloads/{artifact_id}/resolve für jedes artifact auf und installiere erst nach erfolgreicher SHA-256 Prüfung des vollständigen Archivs. Bestehende legacy public file URLs bleiben während der Migration derselben artifact identity zugeordnet und erfordern keinen erneuten archive upload.

Download resolve erstellt oder setzt einen actor-gebundenen dauerhaften download job mit einem stabilen kurzlebigen grant fort und liefert nach erfolgreicher Grant-Aktivierung 200 JSON; er verwendet keinen 302 redirect und streamt keine binary data. Speichere artifact_id, download_job.id, download_job.grant_id und download_session_id, nicht primary_url, head_url oder fallback_url. primary_url ist für GET mit Range und If-Match/If-None-Match/If-Modified-Since/If-Unmodified-Since; die separat signierte head_url ist für HEAD mit Content-Length und ETag. Nutze expires_at, expires_in und refresh_after_seconds, um POST /v1/downloads/{artifact_id}/resolve erneut aufzurufen, dieselben hashes.sha256/sha256 und size_bytes zu prüfen und nach URL-Ablauf ab dem letzten dauerhaften Byte fortzusetzen. Fallback nutzt POST /v1/downloads/{artifact_id}/fallback mit downloadSessionId und erzeugt nur dann eine weitere signed URL, solange der grant aktiv ist.

hashes.sha256 und der legacy sha256 alias sind der application-level SHA-256 der vollständigen unveränderlichen gespeicherten Objektbytes. Nach normalem HTTP decoding muss der Client das vollständige Objekt aus allen Range-Teilen in Byte-Reihenfolge zusammensetzen, size_bytes vergleichen und erst dann den Digest prüfen; eine einzelne 206 response wird durch diesen full-file hash nicht separat verifiziert. ETag ist kein kryptografischer Digest: Er ist ein opaque Validator für eine transport representation, conditional requests und resume. Sein Wert kann sich nach refresh oder provider failover ändern; übernimm deshalb keinen alten ETag/If-Match in den fallback. Signed transports müssen stored bytes ohne transcoding erhalten; der aktuelle Vertrag garantiert RFC 9530 Content-Digest oder Repr-Digest nicht, daher nutzen Clients die response-body hash fields.

Der Zugriff ist durch user/auth context, client/app identity, files:download oder delegated download scope, entitlement checks, route rate limits und byte budgets begrenzt. 404 bedeutet unavailable artifact, 401/403 auth- oder entitlement-Problem, 429 backoff, und 410 oder download_session_expired einen abgelaufenen oder nicht mehr nutzbaren grant. Bei 429 beachtet der Client Retry-After, speichert den sicheren download_job aus Problem Details und wiederholt mit job_id, monotonem progress.bytes_received und passendem ETag; wiederholte Limits behalten denselben Job und Grant ohne erneute Provider-Arbeit. Signed URL hosts, provider names und query strings sind opaque transport details und können sich ändern, ohne stabile Clients zu brechen.

Behandle 400 und 422 als Request-Fehler, 401 oder 403 als auth/scope Problem, 404 oder 410 als stale selection, 409 als Konflikt für erneute Planung, 429 als backoff Signal und 5xx als retryable mit jitter. Errors verwenden RFC 9457 Problem Details mit application/problem+json: verzweige nach type oder code, niemals nach lokalisiertem detail. Jeder runtime error enthält retryable und docs_slug. retry_after_seconds und quota_bucket erscheinen nur bei sicherer retry- oder quota guidance, required_scopes nur wenn diese scopes die Autorisierung bestehen lassen, und fields nur für sichere validation details. Die OpenAPI x-moddingflow-error-registry ist die source of truth und entwickelt sich innerhalb von v1 nur additiv; bewahre unbekannte zukünftige codes und nutze den numerischen status als fallback. Typed SDK errors behalten diese Felder im parsed payload zusammen mit headers, request_id und trace_id. Bei 429 warte mindestens Retry-After Sekunden und lies RateLimit-Limit, RateLimit-Remaining und RateLimit-Reset vor jittered exponential backoff. Verwende einen stabilen Idempotency-Key pro logischer write oder deterministic resolve retry; 409 idempotency_conflict bedeutet, denselben body erneut zu senden oder eine neue Operation mit neuem key zu starten. Bewahre X-Request-Id, request_id und trace_id für support logs auf.

Creator Upload und Publish Automation

Das primäre Upload API target ist ein Creator, der ein mod archive lokal oder in CI baut und danach eine new version für einen existing mod veröffentlicht, ohne die browser form zu öffnen. Derselbe Pfad muss für GitHub release oder tag pipelines passen: build archive, run local checks, version draft erstellen oder wiederverwenden, upload direct-to-storage, scan und validation abwarten und erst publish ausführen, wenn die API ready meldet.

Team publishing nutzt individual API keys, OAuth apps oder PATs mit den kleinsten verfügbaren scopes, damit maintainers niemals owner password oder main account session teilen. Tool integrations sollen einen Publish to Moddingflow Button in launcher, editor oder build tool platzieren können und trotzdem denselben machine-readable upload status, request_id und trace_id anzeigen, den CI sieht.

Failure states gehören zum product contract: upload_interrupted hält den upload resumable oder abortable, scan_failed und validation_failed blockieren publish mit Details, permission_denied nennt den fehlenden scope oder project access, quota_exceeded gibt retry guidance zurück, und duplicate_version fordert den Client auf, die version wiederzuverwenden oder zu ändern. Verdächtige Dateien wechseln nach quarantine/review; clients erhalten einen clear status statt silent failure. Das public promise ist sicherere automation als die manual form: direct cloud upload, least-privilege keys, no shared owner credentials, auditable provenance und security checks, bevor eine Datei users erreicht.

Beginne mit dem validate-only endpoint POST /v1/uploads:validate; er prüft sizeBytes, expectedHashes.sha256, archiveManifest und buildMetadata, ohne eine Session, ein storage object oder einen audit write zu erstellen. Creator automation öffnet danach POST /v1/uploads, sendet archive bytes direkt in private Cloudflare R2 über eine signed single PUT URL oder signed multipart parts, erneuert parts mit POST /v1/uploads/{upload_id}/parts, prüft GET /v1/uploads/{upload_id}, schließt mit POST /v1/uploads/{upload_id}/complete ab und storniert mit POST /v1/uploads/{upload_id}/abort. Die Next.js API erhält nur metadata und lifecycle calls, niemals den archive body. Große multipart Sessions bleiben 48 Stunden aktiv, jede signed URL 15 Minuten. status.progress liefert uploadedBytes, percent, totalParts und provider-confirmed completedParts mit ETags, damit Website, CLI und GitHub Action nach einer Unterbrechung nur missing parts fortsetzen. Ein private staging object kann vor complete, size/content type/archive format validation und security processing nicht published werden; ein bounded cleanup job bricht expired multipart uploads ab oder löscht incomplete single objects. Complete liefert einen async job, solange scan, metadata extraction, indexing hooks oder final promotion laufen; poll job.statusEndpoint. queued und processing sind non-terminal, succeeded, rejected, failed und cancelled sind terminal. job.progress zeigt ehrlich 0/1 completion statt eines geschätzten scan percentage. createdAt, updatedAt, startedAt, completedAt, retry attempts, nextAttemptAt und der stabile terminal reason unterscheiden active, retrying und finished work, ohne provider prose offenzulegen. Kanonische lifecycleStatus Werte sind draft, uploading, uploaded, validating, scanning, quarantined, ready, published, failed und cancelled; legacy status bleibt ein compatibility alias. expectedSha256 bleibt ein legacy alias und sha256_mismatch lehnt den Upload vor promotion ab. Idempotency-Key bleibt für create und complete retries stabil. Version request metadata unterstützt version, changelog, file_category, game_slug, game_versions, loader_platforms, dependencies, requirements und build_metadata; eine wiederholte logische Version liefert duplicate_version. metadata-only edits erfordern keinen erneuten upload bytes. Der security worker claimt durable jobs atomar, wiederholt transient failures und verschiebt exhausted oder ungeprüfte uploads in quarantine. Die ZIP policy liefert archive_dangerous_entry, archive_encrypted_entry, archive_symlink_entry, archive_duplicate_path oder archive_path_traversal; nested archives und executables erfordern manual review.

Numerische Upload-Defaults: Single PUT bis 100 MiB; Multipart Parts mit 16 MiB und Concurrency 4; ein Signing Request enthält höchstens 4 Parts und begrenzt die Payload in Flight auf 64 MiB; ein Archiv ist auf 20 GiB begrenzt. Der authentifizierte Upload-Byte-Bucket beträgt 24 GiB. Ein Live-R2-Capacity-Probe bestätigte 64 MiB Upload/Download mit gleichem SHA-256, RSS Delta unter 384 MiB und verpflichtendem Cleanup; das Profil 64 MiB × 4 wurde wegen Überschreitung des Memory Budgets abgelehnt.

Wenn complete einen nicht terminalen Job mit Status queued oder processing erzeugt, lautet die HTTP-Antwort 202 Accepted und der Location-Header entspricht exakt job.statusEndpoint. Ist der Job bereits terminal (succeeded, rejected, failed oder cancelled), einschließlich eines idempotenten Replays dieses terminalen Ergebnisses, liefert complete 200 OK, ohne den Side Effect zu wiederholen.

Ein Replay von POST /v1/uploads mit demselben Body und Idempotency-Key liefert die ursprüngliche durable Session mit neu signierten Upload-Anweisungen. Jede single PUT URL hat höchstens eine 900-second Lease, die der Server nach dem Signing und vor der Response unter Row Lock checkpointet; ein abort-winning Checkpoint verwirft die URL-Response. Sende alle zurückgegebenen Header einschließlich If-None-Match: *. Ein conditional 412 ist ein ambiguous existing write: Überschreibe Staging nicht, sondern rufe denselben complete Endpoint auf, wo HEAD, immutable Seal und SHA-256 authoritative sind. Signed URLs und Provider-Upload-IDs werden nie im generischen Idempotency-Datensatz gespeichert; prüfe nach einem mehrdeutigen Create-Timeout zuerst die Upload-Session, bevor du eine neue Mutation startest.

Nutze für polling guidance retry.retryable, retry.strategy, retry.attempt, retry.maxAttempts, retry.nextAttemptAt und retry.retryAfterSeconds. Eine HTTP response kann dieselbe Mindestverzögerung über Retry-After angeben.

Das manuelle Mod-Upload-Formular der Website nutzt dieselben Upload-Session-Dienste und dasselbe private R2-Backend. Es zeigt Dateiübertragung und Sicherheitsprüfung, bewahrt einen erstellten Mod-Entwurf nach einer Netzwerkunterbrechung auf und verwendet vom Speicheranbieter bestätigte Multipart-Teile erneut, wenn dieselbe Datei gewählt wird. Vor dem Multipart-Commit prüft der Server die exakte Größe und den Provider-ETag/MD5 jedes Teils erneut; danach verifiziert der asynchrone Trust-Worker den SHA-256 des gesamten Objekts vor ready/publish. Unter Einstellungen > Entwickler siehst du aktuelle Sessions, Scan-Fehler sowie den Quarantäne- und Prüfstatus.

Wähle die manual browser form für einen gelegentlichen geführten Release. Nutze Upload API für wiederholbare local tools, CI und GitHub Actions. Beide Wege verwenden dasselbe upload-session backend, private Cloudflare R2 storage, validation, scan, quarantine/review und ready-before-publish security gates.

Gib jeder Person oder automation einen individual resource-scoped key mit den smallest required scopes. Das raw secret wird one-time angezeigt. Speichere MODDINGFLOW_API_KEY in einem GitHub environment or repository secret, niemals in workflow YAML, repository variables, logs, command arguments oder artifacts. Nutze protected environments, minimal workflow permissions und einen geprüften 40-character Action commit SHA.

Nach scan_failed oder av_scan_rejected darfst du nicht veröffentlichen oder das Archiv neu verpacken, um das verdict zu umgehen. Halte die Datei private, frage GET /v1/uploads/{upload_id} ab, notiere supportCorrelationId oder X-Upload-Correlation-Id, request_id, trace_id und error code und folge danach quarantine/review, manual review oder support guidance. Die correlation id darf mit dem support geteilt werden: Sie enthält kein secret und gewährt keinen Zugriff. Erstelle eine new session erst nach Behebung der erkannten Ursache.

Öffne die FAQ für Workflow-Entscheidungen und die API reference für Status-, Fehler- und Scope-Schemas.

Game, Mod, ModVersion, Artifact, Blob, UploadSession, DownloadGrant, User, Team, Dependency, Job, WebhookSubscription und WebhookDelivery besitzen jeweils eine eigene opaque UUID, eine Ownership-Regel und eine fail-closed State Machine. Slug, Benutzername, Teamname, Dateiname, URL, Storage Key, ETag oder Hash sind keine öffentliche Identität. Speichere die zurückgegebene UUID; eine Umbenennung ändert sie nie.

Archivierung ist reversibel, eine dauerhafte Löschung erzeugt jedoch einen aufbewahrten Tombstone. Eine unbekannte UUID liefert 404; eine tombstoned UUID liefert 410 und wird nie wiederverwendet. Der kanonische Versions-Lifecycle lautet draft → validating → scanning → ready → published → archived/withdrawn; failed ist ein explizites Ergebnis. uploaded, quarantined, approved und rejected bleiben Kompatibilitäts-Aliase für alte Clients.

Bei der ersten Veröffentlichung speichert der Server einen unveränderlichen Snapshot aus Version, Dateityp, Release-Kanal, Spiel- und Loader-Bindungen, Blob-UUID, Größe und Inhaltstyp sowie dessen SHA-256. Diese Felder können danach nicht in-place geändert werden; Restore aus archived/withdrawn wird nur akzeptiert, wenn der Snapshot exakt übereinstimmt und die aktuellen Upload-Trust-Gates erneut bestehen. Öffentliche parallele Mod/ModVersion PATCH Requests verwenden einen starken ETag in If-Match: Ohne Validator folgt 428, bei einem stale validator 412 mit dem aktuellen ETag. lifecycle_version bleibt das interne optimistic lock für Lifecycle-Transitions.

Publishing automation erstellt oder aktualisiert einen mod draft, erstellt einen version draft, hängt ein clean upload artifact an, validiert metadata und ruft publish erst auf, wenn der Creator bereit ist. Nutze mods:write für mod metadata und versions:write für version changes.

Katalog- und Suchintegrationen

Für catalog integrations nutze 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 und GET /v1/search. List und search nutzen cursor-first keyset pagination mit cursor, limit (default 25, max 100), stable tie-breaking und opaque v1 base64url cursor values; gib den cursor aus der vorherigen Response weiter und erzeuge keine offset oder page-number cursors. Jeder cursor ist an resource, normalized filters, sort und den insertion snapshot der ersten Seite gebunden. Wiederverwendung mit geändertem Kontext liefert 400 invalid_input; nach Seite eins erstellte oder veröffentlichte Zeilen bleiben außerhalb dieses cursor snapshot. GET /v1/mods und GET /v1/search akzeptieren die filters game_slug, q, sort=relevance|latest|trending|downloads, game_version, loader und category; der website compatibility namespace akzeptiert außerdem die aliases game/gameSlug, query/search, gameVersion/version, platform und facet. Search erfordert q und nutzt standardmäßig sort=relevance, während list ohne query standardmäßig latest verwendet. 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.

Anonyme Catalog-GET-Responses verwenden Cache-Control public, einen weak ETag und Vary: Accept-Encoding, Authorization, Cookie. Sende den gespeicherten ETag als If-None-Match, um bei unveränderter Representation eine leere 304-Response zu erhalten. Ein Request mit Authorization oder Cookie ist immer private no-store und verwendet den public validator nicht, sodass authentifizierte und anonyme Responses keinen Cache-Eintrag teilen.

Public payloads verwenden stabile UUIDs für games, mods, versions, artifacts und dependencies. Verlasse dich nicht auf website forum topic IDs, storage object names oder human-readable slugs als durable integration keys.

Renames, metadata rebuilds und slug changes behalten bestehende mod, version, artifact, upload-session und download-grant identifiers. Das Ersetzen von Datei-Bytes erzeugt einen neuen physical blob und kann eine neue logical artifact/version binding erzeugen; metadata-only edits erzwingen keinen erneuten Datei-Upload durch Clients.

Das stabile v1 model trennt mod, version, logical file, physical blob, upload-session, download-grant, token und webhook-delivery. Ein mod besitzt versions, eine version verweist über artifact ids auf logical files, und ein logical file kann an einen immutable physical blob im storage gebunden sein, ohne storage paths offenzulegen.

Behandle slug und localized slugs als mutable aliases, filename als display/download text und hash values wie sha256 oder hashes.sha256 als integrity checks. Speichere public ids und lifecycle ids: mod id, version id, artifact id, sessionId, download_session_id, token metadata id und webhook delivery id, sobald webhook delivery endpoints veröffentlicht sind.

PublicModVersion.artifact_ids bleibt die stabile Verknüpfung. Optional PublicModVersion.artifacts erscheint nur für ein managed primary artifact, wenn size_bytes > 0, sha256/hashes.sha256 exakt sind, die current upload session sowohl file_id als auch final_blob_id abgleicht, actual AV scan_status=clean ist, lifecycle=ready, publication state=published und managed resolve eligible ist; andernfalls wird die Erweiterung omitted. Das vollständige Objekt enthält id, mod_id, version_id, file_kind, ein non-null label, nullable original_filename, content_type, game_version_key und loader_key, danach size_bytes, sha256, hashes, artifact_source, status, scan_status und download_metadata. Actual AV states sind pending, queued, scanning, clean, quarantined, rejected, failed und skipped. download_metadata enthält einen relative resolve_endpoint und einen wahrheitsgemäßen range_supported-Wert, aber nie eine provider URL.

Agent Gateway delegated tools

Agent Gateway stellt enge tools für mods.search, mods.get, install_plans.resolve, downloads.resolve, versions.compare, publisher.draft und publisher.validate bereit. Agents sollen tools aufrufen statt Seiten zu scrapen oder arbitrary URLs zu fetchen.

Ein Agent erhält ein kurzlebiges token mit audience agent-gateway und engen tool scopes. Er bekommt nie den vollständigen user session token. Read tools können direkt laufen; write-capable tools bleiben dry-run, bis ein confirmation token die konkrete Aktion erlaubt.

Auth und URL Grenzen

API base URL: https://api.moddingflow.com/v1. OAuth issuer URL: https://api.moddingflow.com. Website compatibility namespace: https://www.moddingflow.com/api/v1. Die official CLI bietet außerdem ein local sandbox profile unter http://127.0.0.1:3000/api/v1; protected staging nutzt die vom Operator gelieferte MODDINGFLOW_STAGING_API_BASE_URL und einen separaten staging key statt eines anonymen shared credential.

Nutze die API base URL für /v1 resources. Nutze die OAuth issuer URL für /oauth/authorize, /oauth/token, /oauth/device_authorization, /oauth/revoke, /oauth/introspect und RFC-8414-Metadaten unter /.well-known/oauth-authorization-server. OIDC-Clients nutzen /.well-known/openid-configuration und /.well-known/jwks.json; ein 503 bedeutet ein OAuth-only Deployment, an das der openid scope nicht gesendet werden darf. Nutze den website compatibility namespace nur, wenn eine browser-hosted integration auf www.moddingflow.com bleiben muss.

Stable v1 nutzt REST/JSON over HTTPS. Read endpoints bleiben GET resource routes, einschließlich GET /v1/uploads/{upload_id} für upload-session status, während write endpoints POST/PATCH mutations bleiben und lifecycle actions explizite action routes wie /v1/uploads/{upload_id}/complete, /v1/mods/{mod_id}/versions/{version_id}/publish und /v1/install-plans:resolve nutzen. Public clients adressieren Dateien über artifact_id, upload_id und download_session_id, nie über storage object keys, provider bucket names oder signed URL secrets.

Der Desktop Mod Manager und user-created public applications öffnen https://api.moddingflow.com/oauth/authorize mit response_type=code, einem S256 code_challenge, requested scopes, state und allowlisted redirect URI. Nutzer sehen die Anwendung und die angeforderten scopes und müssen den Zugriff ausdrücklich erlauben oder ablehnen. Sensible scopes brauchen vorheriges review der Anwendung und eine aktuelle AAL2-Bestätigung mit 2FA. Der Client tauscht den authorization code bei https://api.moddingflow.com/oauth/token gegen Tokens und speichert nie das Benutzerpasswort. Für eine Identity Assertion wird openid mit einem frischen nonce von 8-512 Zeichen angefordert; die Token-Antwort enthält dann ein fünf Minuten gültiges RS256 ID Token, dessen Signatur, issuer, client_id audience, iat/exp und nonce vollständig geprüft werden müssen. Das ID Token darf nie als /v1 Bearer Access Token verwendet werden.

Refresh Tokens wechseln bei jedem refresh. Wenn ein Client einen älteren refresh token erneut verwendet, widerruft der Server sofort die gesamte refresh family und verlangt eine neue Autorisierung.

CLI- und fallback clients erstellen einen device grant über https://api.moddingflow.com/oauth/device_authorization, zeigen user_code und verification URI und pollen dann https://api.moddingflow.com/oauth/token. Die Bestätigungsseite zeigt Client und requested scopes und erlaubt Nutzern, die Anfrage zu genehmigen oder abzulehnen. Polling darf nicht schneller als interval sein; nach slow_down muss der backoff steigen, und bei expired_token oder access_denied muss es stoppen.

Entwickler registrieren OAuth applications unter Einstellungen > Entwickler auf /account/developer/applications. Ein neuer public client erhält client_id, registriert exakte HTTPS-, custom-scheme- oder loopback redirect URIs, wählt Device Flow support, nutzt PKCE S256 und kann mit basic read/install scopes ohne manuelle moderation starten: mods:read, files:download, install_plans:resolve und profile:read. Jeder Nutzer sieht die ausdrückliche Zustimmung zu den Scopes und die account-wide resource statement: genehmigte OAuth permissions gelten für alle Ressourcen, auf die der Nutzer zugreifen darf. Für automation, die auf ausgewählte Mods oder Projekte beschränkt sein muss, wird ein resource-scoped PAT verwendet.

Sensitive grants brauchen review, bevor sie an die app ausgegeben werden: mods:write, versions:write, files:upload, webhooks:write, private_data:read, agent:* und admin:*. Das Erteilen sensibler scopes braucht außerdem eine aktuelle AAL2-Bestätigung mit 2FA. Unknown scopes fail closed, und admin:* ist für user-created applications nie verfügbar. Das Deaktivieren einer Anwendung wirkt sofort für authorization, token issuance, refresh und access-token checks.

Sende User-Agent als ApplicationName/ApplicationVersion und nenne im Kommentar eine gepflegte Kontakt-URL oder E-Mail. PAT mutations erfordern X-Moddingflow-Client und X-Moddingflow-Client-Version gemeinsam. Bei OAuth ist client_id maßgeblich; ein gesendeter Application-Name-Header muss damit übereinstimmen. Fehlende, generische, fehlerhafte oder abweichende Identity liefert 400 invalid_client_identity vor Quota-Verbrauch und Business-Side-Effects. Die offiziellen TypeScript-, Python-, C#-, CLI- und GitHub-Action-Clients senden standardmäßig stabile Identity-Header.

Individual API keys und Personal Access Tokens sind für user-owned automation gedacht. POST /v1/api-keys und POST /v1/api-keys/{api_key_id}/rotate erfordern Idempotency-Key. Derselbe Key und Body wiederholen die Mutation nie; wenn das one-time secret nicht mehr sicher wiederhergestellt werden kann, schlägt der Replay mit idempotency_replay_unavailable ohne operation-shaped committed metadata fehl. Prüfe den Key über GET /v1/api-keys oder widerrufe ihn, bevor du einen neuen idempotency key verwendest. POST /v1/api-keys zeigt das raw secret genau einmal; gespeichert werden nur token_hash, ein safe fingerprint/display_prefix, scopes, resource grants, last_used_at, expires_at und rotation/revoke state. GET /v1/api-keys liefert nur sichere metadata, während GET /v1/api-keys/capabilities verfügbare scopes, mod projects und die team role vor der Key-Erstellung zeigt. Dieselben Aktionen stehen angemeldeten Autoren unter Einstellungen > Entwickler zur Verfügung, ohne die account session cookie an externe tools weiterzugeben.

POST /v1/api-keys/{api_key_id}/rotate erstellt atomar ein replacement mit denselben scopes, resource grants und expiry und zeigt das neue secret einmal. Das alte secret bleibt nur für einen maximalen 10-minütigen overlap gültig und wird danach automatisch widerrufen. DELETE /v1/api-keys/{api_key_id} führt einen sofortigen idempotent revoke aus. Der owner verwaltet team publishing über /v1/mods/{mod_id}/team-members: owner, maintainer, publisher und reviewer nutzen ihre eigenen user identities; das audit log speichert key id/fingerprint, aber niemals ein secret.

Fordere nur die scopes an, die zur Persona passen: mods:read für catalog, mods:write für metadata, versions:write für version drafts, files:upload für upload, versions:publish für publish, versions:archive für archive/rollback, files:manage für file management und teams:admin für owner-only team management. files:download und install_plans:resolve gelten für Mod Manager installs, webhooks:read/webhooks:write für webhook management, private_data:read für private user-owned data und agent:* für delegated tools. admin:* bleibt trusted internal clients vorbehalten.

OAuth-, PAT-, upload-, download- und Agent-Gateway-calls erhalten rate limits vor storage, signing, upload, download oder tool side effects. Buckets werden pro application/client_id, pro user/actor_user_id und pro token_id_or_api_key_id bewertet; scope und token_kind fließen in policy matching ein.

Bei 429 müssen clients Retry-After und vorhandene RateLimit-Limit, RateLimit-Remaining und RateLimit-Reset headers beachten. Ein anderes PAT oder eine andere OAuth app darf nicht genutzt werden, um einen user oder token abuse bucket zu umgehen.

Runnable examples

Öffne das OpenAPI JSON, konfiguriere apiBaseUrl https://api.moddingflow.com/v1 und oauthIssuerUrl https://api.moddingflow.com und wähle den Job: Mod Manager install/download, creator upload/publish, catalog/search oder Agent Gateway. Website compatibility namespace: https://www.moddingflow.com/api/v1. Für creator automation prüft npm run api:cli -- auth check das Credential der Umgebung; npm run api:cli -- publish --mod-id <id> --archive <path> --version <version> führt validation, direct upload, scan polling und publish aus. Separate upload create, resume, complete und status commands verwenden eine explizite geschützte 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 startet mit POST https://api.moddingflow.com/v1/uploads, sendet bytes direkt an storage, erneuert multipart signed parts bei Bedarf, pollt GET https://api.moddingflow.com/v1/uploads/{upload_id}, completes upload, wartet auf ready oder failed, erstellt dann einen version draft und veröffentlicht. Verwende einen Idempotency-Key pro logischer create/update Operation, damit retries keine duplicate drafts erzeugen.

Reference, SDK und package policy

Nutze die API reference page für endpoint list, request/response schemas, scopes, servers, deprecation metadata und examples aus OpenAPI.

Bis packages veröffentlicht sind, gilt das SDK als repo-only source artifact. Öffne den SDK source page mit source archive links und connection snippets, nutze danach repo/local imports: TypeScript imports sdk/typescript/src/moddingflow-public-api.ts, C# references sdk/csharp/Moddingflow.PublicApi, und Python adds sdk/python to PYTHONPATH before importing sdk/python/moddingflow_public_api. SDK source ist noch not published to npm, PyPI, or NuGet. Lokalisierte Textfelder wie title, summary und description bleiben language-code maps: TypeScript exposes [key: string]: string, Python exposes dict[str, str], and C# exposes Dictionary<string, string>. Python request DTOs behalten OpenAPI required fields mit Required[...] und NotRequired[...], sodass type checker install-plan bodies ohne game_slug oder game_version melden. Generierte Clients führen standardmäßig höchstens drei Versuche aus und wiederholen nur sichere/idempotente Methoden oder Schreibvorgänge mit Idempotency-Key. Sie berücksichtigen Retry-After und RateLimit-Reset mit begrenztem exponentiellem Backoff und Jitter, stellen die finalen Request-/Trace-IDs bereit und können abgelaufene signierte Download-URLs über einen expliziten Resolve-Hook erneuern. Regenerate and verify local artifacts with npm run api:sdks:generate und npm run api:sdks:check. Das Release Gate führt zusätzlich npm run api:sdks:compile aus; dabei werden npm-, Wheel- und NuGet-Artefakte lokal gepackt und in echten Consumern installiert sowie CLI und GitHub Action geprüft, ohne Registry-Pakete zu veröffentlichen.

Die Support/EOL-Matrix in docs/public-api/SDK-SUPPORT-AND-EOL.md führt jede SDK-, CLI- und Action-Version getrennt vom contract release 1.0.0-foundation sowie runtime floors und distribution state auf. Die aktuellen Artefakte sind Preview-Quellpakete ohne Registry-Verfügbarkeit, SLA, Signatur oder stabile EOL-Zusage. Ein stabiles Supportfenster beginnt erst nach einer clean immutable publication und einer Aktualisierung der Matrix mit datierten Zusagen.

Backwards compatibility policy: stable v1 old clients laufen während des ganzen support window weiter. Public operationIds, fields, scopes, error codes, headers, cursor semantics, idempotency und lifecycle behavior dürfen nicht entfernt, umbenannt oder in einen incompatible type geändert werden, außer über eine neue major version oder einen abgeschlossenen deprecation path. Compatible additive fields können im aktuellen v1 contract erscheinen, wenn old clients sie ignorieren können; new request fields müssen optional, nullable oder mit defaults versehen sein. New endpoints können in v1 ergänzt werden, wenn sie keinen bestehenden stable flow ersetzen und kein versteckter prerequisite werden. New enum/status values brauchen tolerant client guidance, docs und SDK updates. Beta oder experimental endpoints müssen marked with x-moddingflow-stage=beta, x-moddingflow-stage=experimental sein oder in einem future /v1/beta/* path liegen, und sie dürfen nicht required for stable flows sein. Deprecated operations und HTTP-visible capabilities nutzen den Deprecation response header als RFC 9745 Structured Field date, Sunset als RFC 8594 HTTP-date und Link rel="deprecation" für den migration guide. Breaking removals brauchen at least 90 days warning oder two stable API releases, einen migration guide mit replacement, detection, rollout, deadline und support contact sowie einen dedicated changelog entry. Any deprecated OpenAPI operation must include x-deprecation-date, x-sunset-date, x-migration-url, and x-changelog-url; das OpenAPI lint gate lehnt unvollständige deprecation metadata ab. Das Repository Release Gate lehnt Sunset außerdem ab, solange die genauen Daten nicht an committed migration, ready replacement, dated telemetry und approved support-owner evidence gebunden sind.

Release Notes, Migration-Guide-Vorlagen und stable-v1 Changelog Evidence stehen auf der API-Changelog.

Webhook automation

Nutze POST /v1/webhook-subscriptions mit webhooks:write für eine owner-scoped Subscription auf mod.updated, version.published, file.ready, file.failed, upload.completed, scan.completed, token.changed oder app.changed. Targets müssen öffentliches HTTPS auf Port 443 verwenden. Die API gibt das Vault-backed whsec_ signing_secret einmal zurück; POST /v1/webhook-subscriptions/{subscription_id}/rotate-secret liefert eine neue Version mit begrenztem Overlap Window. Prüfe X-Moddingflow-Signature als HMAC-SHA256 über `${timestamp}.${raw_body}` mit X-Moddingflow-Timestamp und lehne stale Timestamps ab. Der versionierte Body enthält spec_version, stabile event_id, delivery_id, type, version, occurred_at, opaque Subject-IDs und data. Delivery ist at-least-once und ungeordnet: schnell bestätigen, asynchron verarbeiten, nach event_id deduplizieren und den neuesten State abrufen. Automatic Retries nutzen exponential backoff und enden in exhausted. Manual Redelivery über POST /v1/webhook-deliveries/{delivery_id}/redeliver mit Idempotency-Key erstellt eine neue delivery_id mit Link zum Original und behält event_id. GET /v1/webhook-deliveries mit webhooks:read zeigt delivery_status, attempt_count, next_attempt_at, last_response_status, begrenzte Latenz-, bereinigte Response-Snippet-, Retry- und Link-Daten. Wiederholte terminale Fehler können disable the subscription.

Bei Upload-Trust-Übergängen hält scan.completed das unveränderliche Scan-Ergebnis einschließlich Quarantäne fest. Eine spätere manuelle Freigabe erzeugt ein separates file.ready-Event; kein logisches Ereignis wird überschrieben, und Empfänger können beide unabhängig deduplizieren.

Operations, limits und support

Eine erfolgreiche Version-Mutation-Response ist das authoritative committed Ergebnis dieser Operation und immer private, no-store. Anonyme Catalog-/Version-GETs revalidieren den shared cache, behalten einen weak representation ETag und verwenden public, max-age=0, s-maxage=0, no-cache, must-revalidate. Das Catalog-Freshness-SLO ist p95 höchstens 15 Sekunden, Search-Index-Freshness p95 höchstens 1800 Sekunden; das Dashboard nutzt das eigene Fenster jedes Ziels und berücksichtigt das Alter unfertiger Jobs, daher kann es nach Recovery ehrlich critical bleiben. Catalog-Visibility ist nie die Commit-Bestätigung; lösche keine Historical Evidence für ein grünes SLO. Mod/ModVersion PATCH erfordert genau einen strong quoted If-Match aus Create, authenticated Detail oder dem vorherigen PATCH: Fehlt er, folgt 428 precondition_required; malformed, weak, wildcard oder list values liefern 412 precondition_failed ohne Mutation; ein syntaktisch gültiger stale validator liefert 412 mit dem aktuellen ETag. Nach 412 Detail abrufen, Änderung rebasen und den neuen ETag mit einem neuen Idempotency-Key verwenden; kein blind retry.

Die cached PublicModVersion artifact expansion erfordert actual AV scan_status=clean, lifecycle=ready und publication state=published und bleibt eventual. Direct Download Resolve und Install-plan Resolve sind private point-in-time reads über ein eligible target, dessen established database state approved oder published sein kann. Direct eligibility ist nicht published-only und nicht legacy-only; fordere Resolve erneut an, wenn sich Version, Artifact, Dependency, Entitlement oder Publication State geändert haben könnten.

GET /v1/uploads/{upload_id} liefert den current database state zum Request-Zeitpunkt, während Worker-Transitions eventual sind. Poll Upload Status und job.statusEndpoint bis zu einem terminal state. Provider Progress ist nur bei progress.available=true authoritative; progress.available=false bedeutet unknown, nicht zero bytes und nicht den Verlust akzeptierter multipart parts.

Ein Install Plan ist ein deterministic private/no-store request-time snapshot mit ETag und keine live subscription. Resolve ihn nach einer späteren Änderung an Version, Artifact, Dependency, Entitlement oder Publication erneut.

Webhooks sind feature-gated. Wenn Management und Delivery aktiviert sind, ist Delivery eventual, at-least-once, replayable und unordered: Antworte schnell mit 2xx, deduplicate nach event_id, tolerierte replay und reordering und fetch den latest state. Webhook Arrival bestätigt keine Mutation; solange einer der Gates deaktiviert ist, wird keine Delivery versprochen.

Die Pipeline bleibt fail closed und behauptet keine cross-provider transaction. Create committet zuerst einen once-only Byte-Budget-Checkpoint für eine Request-Hash-Generation und reserviert danach Session/File Intent zusammen mit dem upload.accepted Audit atomar vor Provider Work. Eine verlorene Antwort zwischen beiden Checkpoints spielt dasselbe Budget-Ergebnis erneut ab; Exact Replay berechnet und auditiert daher nicht doppelt. Jeder single PUT wird mit If-None-Match: * signiert; seine maximale 900-second URL Lease wird nach dem Signing und vor der Response unter Row Lock checkpointet, und ein abort-winning Checkpoint verwirft die URL. Ein conditional 412 ist ein ambiguous existing write: Der Client ruft complete auf, wo Server HEAD, immutable Seal und SHA-256 authoritative sind. Nach Provider Completion kopiert ein gemeinsamer Session-Level Claim den exakten Client-Source-ETag bedingt in einen write-once server-only Seal. Der Seal Key wird vor CopyObject gespeichert, eine verlorene Copy-Antwort wird durch exakte HEAD-Prüfung wiedergefunden, und konkurrierende Completion Keys können den gebundenen Seal nicht überschreiben. Die immutable Identity erreicht den queued scan checkpoint mit pending SHA; der fenced Worker streamt und attestiert den vollständigen Hash atomar vor Validation, AV oder Final Copy. Nach einer verlorenen Antwort wiederhole denselben normalisierten Request mit demselben Idempotency-Key; nach Checkpoint und Job genügt Status-Polling.

Eine content-addressed final copy ist noch nicht ready. Same-SHA Promotion wird durch einen dauerhaften SHA/Bucket/Key Claim serialisiert: Der Gewinner besitzt das canonical MIME und nutzt den Cloudflare Header cf-copy-destination-if-none-match: *, während ein fresh duplicate wartet oder den exakten ready Blob übernimmt, ohne einen normalen scan attempt zu verbrauchen. Das Contention-Polling wächst exponentiell von einer auf 30 Sekunden und ist pro Claim auf 15 Minuten begrenzt. Die ersten zwei dauerhaften token-fenced Yields stellen den normalen Attempt atomar wieder her und stellen den Job mit einem von der Datenbank bestimmten Jitter von 30 bis 60 Sekunden erneut in die Queue; der dritte erstellt atomar Manual Review und Quarantäne, statt unbegrenzt weiter zu versuchen. Lost-Response-Replay liefert dasselbe persistierte queued oder quarantined Ergebnis. Eine verlorene Copy-Antwort wird nur akzeptiert, wenn HEAD SHA-Metadata, Größe, canonical MIME und einen nonblank Provider-ETag validiert. Ein accessible attested blob muss existieren, bevor ein atomic ready binding den immutable Blob, das Logical File und die Upload Session zusammen mit Audit und file.ready Event verknüpft. Während der manuellen Prüfung bleibt die Quarantäne bis zur Provider-Attestierung bestehen: Eine Freigabe committet Ready-Binding, Review-Entscheidung, Decision Audit und Event atomar; eine Ablehnung committet rejected Session, Datei, Review, Decision Audit und Event atomar. Transactional publish und durable outbox commit erfolgen in einer Database Transaction; Metadata kann vor dem Ready Binding nicht veröffentlicht werden.

Heartbeat-backed Claim Tokens grenzen jede Scan-Mutation ab: Ein stale Worker kann nach Reclaim weder Retry noch Clean-, Quarantine-, Rejection-, Audit- oder Event-State ändern. Jeder neue Ready-, Quarantine- oder Rejection-Übergang committet Session, Datei, Job, Audit und das deterministische kanonische Event in einer Database Transaction. Exact Replay validiert das vorhandene terminale Envelope; nach einer uneindeutigen RPC-Antwort akzeptiert der Worker nur das exakt neu geladene Envelope. Bounded Reconciliation repariert nur explizite Legacy Gaps und überspringt aktive Claims. Deployed webhook delivery wird nicht versprochen. Account-Inbox-Benachrichtigungen sind keine Lifecycle Acknowledgements; maßgeblich bleibt der machine-readable Upload-Status.

Vor Provider-Konfiguration oder normalen Scan Claims stellt ein begrenzter reiner Datenbank-Sweep stale Processing Claims mit ausgeschöpften Versuchen wieder her, ohne Storage- oder Scan-Arbeit zu wiederholen. Kohärenter State wechselt atomar in canonical Manual Review und Quarantine; inkohärenter State setzt nur den exakten token-fenced Job mit manual_intervention_required terminal auf Failed, damit das Problem sichtbar bleibt, ohne Session- oder File-State zu erfinden. Eine fehlerhafte Zeile blockiert keine unabhängigen Jobs, und Recovery verbraucht dasselbe begrenzte Worker-Batch-Budget.

Recovery wiederholt zuerst den bewiesenen Seal, die content-addressed copy oder das Binding. Cleanup hat einen eigenen Claim Token. Für Single Staging schreibt durable Stage 1 bedingt einen zero-byte tombstone und validiert beim Provider ETag, LastModified, Metadata Token und size=0; der Tombstone bleibt bis zur persisted latest URL Lease und mindestens 16 Minuten bestehen. Durable Stage 2 führt Exact Delete, HEAD-Abwesenheitsprüfung und DB Completion aus; Crash Replay setzt den aktuellen Stage fort. Multipart Cleanup bricht jeden Exact-Key Upload ab und relistet ihn, und jeder Seal-Claim-Key wird gelöscht und per HEAD geprüft; abgelaufene uploaded Sessions ohne Checkpoint sind eingeschlossen, Quarantäne behält ihren Seal. Generation Byte-Budget Reservations werden erst nach Replay Window plus Sicherheitsmarge gelöscht. Die gewünschte CORS Policy enthält If-None-Match, und der Object-Level Probe bestätigt Conditional Copy plus Late-PUT Tombstone Rejection, aber live CORS/lifecycle/lock evidence bleibt BLOCKED bis die Operator Inputs CLOUDFLARE_API_TOKEN, MODDINGFLOW_R2_FINAL_LOCK_MIN_SECONDS und MODDINGFLOW_R2_ALLOWED_BROWSER_ORIGINS vorliegen; ein live Deployment wird nicht behauptet. Eine unbound final copy remains non-ready and non-publishable, ist never publishable und bleibt unter Retention/Lock bis zur PR-F3.13 reference-aware deletion. Numeric freshness bleibt PR-F2.6.3, deployed webhook enablement bleibt PR-F8.14.

Die API wendet route, scope, body-size, byte-budget, download concurrency und abuse limits vor teuren side effects an. Download Leases erlauben 2 anonyme oder 4 authentifizierte Requests pro Actor und 16 pro OAuth/PAT Client, mit 300 Sekunden Ablauf; Byte Buckets sind 24 GiB anonym, 40 GiB authentifiziert und 24 GiB Upload. Multipart nutzt standardmäßig 16 MiB × 4, höchstens vier signed parts pro Request und 64 MiB Payload in Flight. Oversized bodies liefern 413. Overloaded oder abusive clients erhalten 429 mit Retry-After und RateLimit headers sowie retryable, retry_after_seconds und einem nicht sensiblen quota_bucket. Jeder application/problem+json error enthält außerdem code, docs_slug, request_id, trace_id und X-Request-Id; scope- und validation errors ergänzen required_scopes oder fields nur wenn umsetzbar. Error codes entwickeln sich innerhalb von v1 additiv, daher müssen clients unbekannte codes bewahren und auf den numerischen HTTP status zurückfallen.

Bevor du ein neues API artifact nutzt, prüfe, dass OpenAPI, SDK generation, compatibility checks, localized docs und reference rendering für den release bestanden wurden. Stable v1 verlangt zusätzlich 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 und client flow smoke. Zusätzlich braucht es 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 und first stable release notes. Fehlt ein endpoint in OpenAPI, gilt er als website-internal und nicht als public developer surface.