Errors
A failed call returns an HTTP status and a JSON body with a stable code. Branch on the code, not on the message.
The error shape
Pipeline errors come in a detail object with error (the code in lower case), code (the same in upper case) and message (plain words for a person). Many errors add fields: a quote, a lint result, a list of files or issues, a problem id, or the plan and limit that applied.
{
"detail": {
"error": "quote_changed",
"code": "QUOTE_CHANGED",
"message": "The approval does not match this plan. Quote it again.",
"quote": { "...": "extra fields depend on the error" }
}
}A request whose query or header values fail basic validation, such as a limit out of range, is answered by the framework with 422 and a list under detail instead:
{
"detail": [
{ "loc": ["query", "limit"], "msg": "...", "type": "..." }
]
}A Retry-After header comes with the 429 codes and with 503 result_busy. Statuses 304 and 204 on the polling and file routes are not errors.
Retry advice
Each code has one of four kinds of advice. The advice says whether sending the same request again can ever work; it is not a promise about timing.
| Advice | What to do |
|---|---|
| No | Do not retry. The request is wrong, or the thing it asks for is gone. |
| After you change something | Fix the request, the key, the account or the data, then send it again. |
| Yes, the same request may succeed later | Repeat it with a growing wait. |
| Yes, after the Retry-After header | Wait the number of seconds in the header, then repeat. |
Authentication and access
| Code | Status | Meaning | Retry |
|---|---|---|---|
account_frozen | 403 | The account that owns the key is suspended. | No. The same request cannot succeed. |
account_inactive | 403 | The account that owns the key is inactive. | No. The same request cannot succeed. |
ambiguous_credentials | 400 | The request carried both X-API-Key and Authorization. Send one. | After you change something |
api_key_expired | 401 | The key passed its expiry date. Create a new key. | No. The same request cannot succeed. |
api_key_inactive | 401 | The key has been deactivated. | No. The same request cannot succeed. |
api_key_revoked | 401 | The key was revoked. A rotated key also answers this once its 24 hour grace period ends. | No. The same request cannot succeed. |
auth_unavailable | 503 | The server could not check the credential because storage did not answer. It fails closed and never lets a request through. | Yes, the same request may succeed later |
authentication_required | 401 | No credentials were sent, or the Bearer token is neither a pipeline key nor a valid Forge sign-in session. Send the key in X-API-Key (or Authorization: Bearer myrl_...), or sign in again. | After you change something |
grant_disabled | 403 | Pipeline access for this key was switched off. | After you change something |
invalid_api_key | 401 | The key is not recognised, or it is malformed or has a wrong checksum. | After you change something |
ip_not_allowed | 403 | The key has an address allowlist and this request came from an address outside it. | After you change something |
missing_scope | 403 | The key does not hold the scope this route needs. The message names it. | After you change something |
no_pipeline_grant | 403 | The key exists but was created without pipeline scopes. Create a key with a preset or scopes. | No. The same request cannot succeed. |
pipeline_owner_required | 403 | The server is in its single-owner mode and this account is not that owner. | No. The same request cannot succeed. |
pipeline_subscription_required | 403 | The account behind the key has no active paid plan. Nothing is deleted; the key works again once a plan is active. | After you change something |
rate_limited | 429 | The key used up one of its request buckets (read, write, upload or submit). The Retry-After header gives the wait. | Yes, after the Retry-After header |
rate_limiter_unavailable | 503 | The server could not check the rate limit. It fails closed. | Yes, the same request may succeed later |
sound_owner_not_allowed | 403 | The sound pilot is limited to one configured account. | No. The same request cannot succeed. |
too_many_failed_auth | 429 | This address made too many requests with keys the server does not know. The Retry-After header gives the wait. | Yes, after the Retry-After header |
Request
| Code | Status | Meaning | Retry |
|---|---|---|---|
idempotency_key_required | 422 | Submit needs an Idempotency-Key header of 1 to 64 printable ASCII characters. | After you change something |
invalid_content_length | 400 | The Content-Length header is not a number. | After you change something |
invalid_cursor | 422 | The cursor is not one the server issued. Start again from the first page. | After you change something |
invalid_day | 422 | The day is not a real UTC calendar date. | After you change something |
invalid_manifest | 422 | The manifest is not valid JSON of the right shape, or fails the schema. The body lists issues with a code, a where pointer and a message. | After you change something |
invalid_request | 422 | A field is missing, has the wrong type or is not allowed. The body lists each issue under issues. | After you change something |
invalid_slug | 422 | The project slug must be 1 to 48 lowercase letters, digits or underscores, and not reserved. | After you change something |
project_mismatch | 422 | manifest.project.slug does not match the project in the URL. | After you change something |
request_too_large | 413 | The request body is over the byte limit for the route. | After you change something |
zip_file_required | 422 | An upload needs exactly one multipart field named file that holds a ZIP. | After you change something |
Projects
| Code | Status | Meaning | Retry |
|---|---|---|---|
asset_not_found | 404 | No asset with that id in the project. | No. The same request cannot succeed. |
asset_registry_conflict | 409 | A declared asset id belongs to an existing item that the pipeline did not create. | After you change something |
concurrent_update | 409 | Another request changed the same data at the same moment. Repeat the request. | Yes, the same request may succeed later |
job_project_exists | 409 | This mod job already has a project. Reuse its original slug. | After you change something |
job_project_immutable | 409 | A project stays assigned to its original mod job. | No. The same request cannot succeed. |
not_found | 404 | Nothing is here. A route that is switched off on this server answers this too, as if it did not exist. | No. The same request cannot succeed. |
project_budget_admin_required | 403 | A project budget above the ceiling for ordinary accounts needs an administrator account. | No. The same request cannot succeed. |
project_not_found | 404 | No such project for this account, or the key is bound to a different project. | After you change something |
slug_taken | 409 | That project slug is in use. The response suggests a free one. | After you change something |
Inputs
| Code | Status | Meaning | Retry |
|---|---|---|---|
directory_entry | 422 | The ZIP holds directory entries. Include files only. | After you change something |
duplicate_entry | 422 | Two archive paths are the same, ignoring case. | After you change something |
encrypted_entry | 422 | The ZIP holds an encrypted entry. | After you change something |
entry_size_mismatch | 422 | A file inside the ZIP expands past its declared size or the limit. | After you change something |
entry_too_large | 422 | One file inside the ZIP is over the per-file limit once expanded. | After you change something |
expanded_zip_too_large | 422 | The ZIP expands past the total limit. | After you change something |
input_archive_changed | 422 | The stored input ZIP failed its hash check. Upload the inputs again. | After you change something |
input_file_changed | 422 | A stored input file failed its hash check. Upload the inputs again. | After you change something |
input_purpose_conflict | 422 | This ZIP is already stored for a different purpose, for example as evidence. | After you change something |
input_set_not_found | 404 | The input set does not exist or has expired. Upload the inputs again. | After you change something |
invalid_animation_metadata | 422 | Input animation metadata must be a small object. | After you change something |
invalid_input_rights | 422 | A rights tag is not one of original, licensed, analysis-only, ARR, ND or code-only. | After you change something |
invalid_inputs_metadata | 422 | _inputs.json holds a field it does not accept, or names a file that is not in the ZIP. | After you change something |
invalid_png_header | 422 | A PNG in the ZIP has no valid header. | After you change something |
invalid_zip | 422 | The ZIP, or its _inputs.json, cannot be read. | After you change something |
png_dimensions_exceeded | 422 | A PNG is over the side or pixel limit. | After you change something |
special_entry | 422 | The ZIP holds a link or other special entry. | After you change something |
too_many_entries | 422 | The ZIP has more entries than allowed. | After you change something |
unsafe_entry_path | 422 | An archive path is absolute or climbs out of the folder. Use plain relative paths. | After you change something |
unsupported_compression | 422 | Use stored or deflated ZIP entries. | After you change something |
zip_too_large | 422 | The ZIP is over the upload limit. | After you change something |
Files
| Code | Status | Meaning | Retry |
|---|---|---|---|
file_not_found | 404 | No such file for this account, or it is not a PNG or OGG, is over 2 MB, or is gone from storage. | No. The same request cannot succeed. |
file_too_large | 413 | The file is larger than the 256 MB download limit; the detail carries maxBytes. Download the batch result ZIP instead. | No. The same request cannot succeed. |
Batches and credits
| Code | Status | Meaning | Retry |
|---|---|---|---|
active_batch_limit | 429 | The account or the key already has the most active batches it may have. A batch frees its slot when it ends. | Yes, after the Retry-After header |
admission_busy | 503 | The project was busy (another of its batches was being admitted or run) and nothing was admitted. Send the same request again with the same Idempotency-Key after the Retry-After header; the reason field says what was busy. | Yes, after the Retry-After header |
batch_expired | 410 | The batch expired before delivery. | No. The same request cannot succeed. |
batch_not_found | 404 | No such batch for this account. A review also answers this when no batch matches the project and manifest revision. | No. The same request cannot succeed. |
batch_subscription_required | 403 | Starting a batch needs a subscription: Creator runs 3 batches at a time, Pro 8, Max 20. The account has none, so its batch limit is 0. Every other route, including quote and lint, keeps working, and single builds need only credits. | After you change something |
content_required | 422 | A merge or shared-file contribution has no content object. | After you change something |
credit_ceiling_exceeded | 402 | The quote costs more than the maxCredits you sent. The body carries the quote. | After you change something |
idempotency_conflict | 409 | This Idempotency-Key already identifies a different approval. Use a new key for a new approval. | After you change something |
key_batch_credit_cap | 402 | The quote is over this key's per-batch credit cap. | After you change something |
key_daily_credit_cap | 402 | The quote is over what is left of this key's daily credit cap. | After you change something |
lane_paused_for_problem | 409 | Automatic fixes for one lane failed repeatedly, so that lane is paused for your account. Other lanes still submit. The body names the lane and a problem id. | Yes, the same request may succeed later |
lint_failed | 422 | A delivered file failed its delivery validation. The body carries the lint result. | After you change something |
partial_output_refused | 409 | This batch has no partial ZIP. Read result.json for the failures. | No. The same request cannot succeed. |
partial_plan_refused | 422 | allowPartial is false and the plan cannot deliver every requested file. The body lists the files. | After you change something |
plan_submit_limit | 429 | The account used its plan's submits for the hour. The Retry-After header gives the wait. | Yes, after the Retry-After header |
plan_unit_limit | 422 | The submission would run more units than the account may run in one submission; the detail carries limit and units. Reused units do not count. Split the manifest into smaller submissions. | After you change something |
preview_not_found | 404 | No preview exists for that file. | No. The same request cannot succeed. |
project_budget_exceeded | 402 | The quote does not fit the project's credit budget. The body gives budgetCredits, creditsUsed, quoteCredits and remainingCredits. Raise the budget with PUT /projects/{slug} or split the batch. | After you change something |
quote_changed | 409 | The plan no longer matches the planSha256 you sent. Quote again and approve the new quote. | After you change something |
quote_required | 422 | Submit needs planSha256 from a quote and an explicit maxCredits. | After you change something |
recipe_unknown | 422 | The manifest names a recipe or derive rule this server does not have. | After you change something |
result_busy | 503 | Result storage is busy. Retry shortly; the Retry-After header gives the wait. | Yes, after the Retry-After header |
result_bytes_changed | 409 | The stored bytes failed their integrity check. Do not install them. | No. The same request cannot succeed. |
result_expired | 410 | The result ZIP has expired. Submit the work again. | No. The same request cannot succeed. |
result_not_ready | 409 | The batch has no result yet. Keep polling until resultReady is true. | Yes, the same request may succeed later |
result_object_missing | 404 | The result ZIP row is ready but the stored object is gone. Fetch result.zip, which rebuilds it. | After you change something |
result_too_large | 409 | The result ZIP would be over 128 MB. Split the manifest into smaller batches. | No. The same request cannot succeed. |
studio_maintenance | 503 | Forge's model builder is in maintenance, so work that needs it is refused before anything is charged. The Retry-After header and retryAfterSeconds give the wait; GET /pricing shows the maintenance block. | Yes, after the Retry-After header |
Reviews
| Code | Status | Meaning | Retry |
|---|---|---|---|
duplicate_review_asset | 422 | An asset appears more than once in the review. | After you change something |
invalid_review | 422 | The review is not a valid myrlin.asset-review/1 document. | After you change something |
review_asset_unknown | 422 | A reviewed asset is not in the batch result. | After you change something |
review_batch_ambiguous | 409 | More than one batch matches the review. Name one in batch.runId. | After you change something |
review_file_unavailable | 409 | A pinned file version does not match this batch result. | After you change something |
review_file_unknown | 422 | A reviewed file path does not belong to that asset in this batch. | After you change something |
review_result_mismatch | 409 | The review does not match the batch result it names. | After you change something |
review_result_unavailable | 409 | The batch has no final result to review yet. | Yes, the same request may succeed later |
review_totals_mismatch | 422 | The review totals do not match its asset verdicts. | After you change something |
review_zip_mismatch | 409 | The review does not match the batch ZIP. | After you change something |
reviewer_name_too_long | 422 | An asset reviewer name is over 200 characters. | After you change something |
unknown_reviewer | 422 | An asset names a reviewer who is not in the reviewers list. | After you change something |
Evidence
| Code | Status | Meaning | Retry |
|---|---|---|---|
evidence_dimensions | 422 | An evidence image is over 1400 pixels on a side. | After you change something |
evidence_format | 422 | Evidence accepts PNG screenshots and text, log or JSON files. | After you change something |
evidence_mismatch | 422 | An evidence path or sha256 in the review does not match the uploaded file. | After you change something |
evidence_not_found | 404 | The evidence set does not exist, belongs to another project or has expired. | After you change something |
evidence_output_unknown | 422 | Inline evidence names a path that is not an output of the reviewed asset. | After you change something |
evidence_replay_conflict | 409 | This review was already submitted with a different evidence set. | After you change something |
evidence_text_too_long | 422 | An inline evidence excerpt is over 16384 characters. | After you change something |
evidence_upload_required | 422 | The review cites evidence files. Upload them first and pass evidenceSetId. | After you change something |
Notifications
| Code | Status | Meaning | Retry |
|---|---|---|---|
invalid_preference | 422 | The preference names an unknown kind, or tries to turn off a security kind. | After you change something |
notification_not_found | 404 | No such notice for this account. | No. The same request cannot succeed. |
project_bound_key | 403 | Notification preferences belong to the account. A key bound to one project cannot change them. | After you change something |
Operator routes (administrator accounts only)
| Code | Status | Meaning | Retry |
|---|---|---|---|
admin_required | 403 | The route is for administrator accounts. | No. The same request cannot succeed. |
invalid_prefix | 422 | The key prefix has the wrong start or characters. | After you change something |
prefix_too_long | 422 | The key prefix is longer than the displayed prefix. | After you change something |
prefix_too_short | 422 | The key prefix is too short to be safe. | After you change something |
problem_not_found | 404 | No such problem flag. | No. The same request cannot succeed. |
reason_required | 422 | An administrator revoke needs a reason. | After you change something |
too_many_matches | 422 | More keys match the prefix than one revoke may cover. Use a longer prefix. | After you change something |
resize
| Code | Status | Meaning | Retry |
|---|---|---|---|
collection_not_found | 404 | No asset of this project is in the named collection. Check the collection name, or name the assets instead. | After you change something |
model_repaint_unavailable | 422 | A resize asked for texture repaint, which is not offered yet. Resize with texture nearest. | After you change something |
resize_no_assets | 422 | The resize request named no collection and no asset. Name a collection or at least one asset. | After you change something |
resize_nonuniform | 422 | Resize is uniform only: give one factor, not one per axis, and do not set allowStretch. | After you change something |
resize_parent_unavailable | 422 | A stored file the resize starts from failed its integrity check, so it cannot be resized. Rebuild or restore the asset first. | No. The same request cannot succeed. |
resize_presentation_unavailable | 422 | presentation keep_world_size is not offered yet. Use scale_all. | After you change something |
resize_refused | 422 | Some of the named assets cannot be resized; the preview in the detail says which and why. Leave those out, or fix what the preview names. | After you change something |
resize_source_manifest_unavailable | 422 | The manifest of the asset's latest batch could not be read, so the resize has nothing to start from. Rebuild the asset in a new batch first. | No. The same request cannot succeed. |
resize_too_many_assets | 422 | One resize request names more assets than it may. Split it into several requests. | After you change something |
restore_file_not_found | 404 | The file id is not a delivered file of this asset. Read the asset's history for its file ids. | After you change something |
restore_nothing_to_do | 422 | The asset has no delivered files, or the chosen versions already equal its current files. Nothing was changed. | No. The same request cannot succeed. |
restore_version_not_found | 404 | The model has no version with that number. Read the asset's history for its versions. | After you change something |
restore_version_required | 422 | A restore needs toVersion (the model file's version) or a files list. | After you change something |
build
| Code | Status | Meaning | Retry |
|---|---|---|---|
multi_build_subscription_required | 403 | A free account already has a build in progress and asked for another; running several builds at once needs a subscription. Wait for the running build to finish, or subscribe. | Yes, the same request may succeed later |
Failure reasons for model files
These are not HTTP errors. They appear as the reason of a file in a batch result or receipt when a native model job fails. retryable says whether an automatic fix retries the job with a new seed. See Automatic fixes.
| Reason | Retried automatically | Meaning |
|---|---|---|
model_async_required | No | A model unit was asked to run inline instead of through the worker queue. |
model_atlas_size | Yes | The texture sides are not multiples of 32 up to 1024, or differ from the declared canvas. |
model_auth_failed | No | The Claude engine's login was refused; the job is not retried because another seed cannot cure it, and new model jobs wait until a fresh token arrives. |
model_batch_canceled | No | The batch was canceled or stopped. |
model_brief_too_long | No | The rendered style guide (over 4,000 characters), the asset describe (over 3,000) or the whole prompt (over 8,000) is too long; shown as unsupported at quote time and never truncated. |
model_companion_unavailable | No | The texture unit of a finished model job was not waiting. |
model_deadline | Yes | The job ran past its deadline. |
model_engine_failed | Yes | The native create engine failed or produced no usable model. |
model_identity_changed | No | The lane identity changed after the quote; requote. |
model_lane_unavailable | No | The model lane was switched off while the unit was pending. |
model_lint_failed | Yes | The model or texture failed the Hytale delivery rules. |
model_prompt_too_long | No | Contract 1 only: the composed prompt was over 600 characters. Contract 2 plans use model_brief_too_long; a worker may still report this for a request it cannot take. |
model_queue_timeout | No | The job waited in the queue until its batch expired. |
model_quota_stop | No | The worker's seat reached the weekly stop line (88 percent by default). |
model_request_invalid | No | The worker could not use the job request; another seed cannot cure it. |
model_result_unavailable | No | The finished job's files were no longer available. |
model_studio_build_failed | Yes | Studio's builder could not complete the model; Studio refunded its credits. |
model_studio_credits | No | The account had too few credits for the Studio build; nothing was charged. |
model_studio_refused | No | Studio refused the build request (prompt policy, project, account or a changed price); nothing was charged. |
model_verification_failed | Yes | The result was not a strict JSON blockymodel or not a decodable PNG, or the engine's own verification failed. |
model_worker_lost | Yes | The worker stopped renewing its lease. |
Failure reasons for sound files
The same applies to sound files. A receipt line carries the sentence as detail next to the code in reason.
| Reason | Retried automatically | Meaning |
|---|---|---|
sound_backend_unavailable | No | No live sound worker matches the pinned backend. |
sound_batch_canceled | No | The batch was canceled or stopped. |
sound_candidates_rejected | Yes | Both generated takes failed the technical checks (decode, length, level or loop). |
sound_constraint | No | The sound request breaks a constraint (audio.variants, channels, sampleRate, maxSeconds, durationSeconds, seed or loop); the unit's own message names which. |
sound_deadline | Yes | The job ran past its deadline. |
sound_identity_changed | No | The pinned sound backend changed after the quote; requote. |
sound_lane_unavailable | No | The sound lane was switched off while the unit was pending. |
sound_loop | No | The loop settings are inconsistent or do not fit the exact output frame count. |
sound_owner_not_allowed | No | The sound pilot is limited to its configured staging owner. |
sound_path | No | The output path is not a relative sounds/*.ogg path. |
sound_prompt | No | The describe is missing, empty or over the backend limit of 2000 characters. |
sound_prompt_review_too_long | No | The describe leaves too little room under the backend's 2000 characters for the review's reasons; shorten it in a new manifest revision. |
sound_queue_timeout | Yes | The job waited in the queue until its deadline. |
sound_reference_unsupported | No | The sound backend takes text only, not audio references. |
sound_result_unavailable | Yes | The finished job's bytes were no longer available. |
sound_worker_failed | Yes | The worker reported a failure without a more specific reason. |
sound_worker_lost | Yes | The worker stopped renewing its lease. |