Every error the realpng API returns, with its status, its code, what it means and what to do about it.
Every failed call answers with an HTTP status and a JSON body whose code is stable. Branch on code, never on message, which is written for people and can change.
Each table lists every code that status carries. GET /optimize/{id}/file and GET /optimize/{id}/image need no key, so they never answer 401, 402 or 429.
The PUT to an upload_url goes to storage, not to realpng, so its refusals are not in this shape. Handle errors covers them.
The body is not a JSON object. On POST /uploads, size is missing or not a positive whole number, or content_type is missing.
Send a JSON object. For an upload, send the file's exact size in bytes and its content_type.
invalid_image
The body sets none or more than one of image_url, image_base64, image_upload_id, video_url, video_upload_id, audio_url and audio_upload_id. Also sent when an upload id is not a UUID or the image is empty.
Send exactly one file field. An upload id is the id from POST /uploads.
invalid_url
image_url, video_url or audio_url is not http or https, is on a port other than the default, includes a user name or password, or points at a private, loopback or link local address, by IP, through DNS or through a redirect. Also a callback_url that is not https, is on a port other than the default, includes a user name or password, or is a private IP address.
Use a URL anyone on the internet can open, or upload the file.
image_unreachable
The image URL answered with a status other than 200, took too long to answer, or could not be fetched.
Check that the URL opens without a login or cookies. If the host blocks automated requests, upload the file instead.
video_unreachable
The video URL answered with a status other than 200, took too long to send the whole file, or could not be fetched.
Check that the URL opens without a login or cookies. A large video goes faster as an upload.
audio_unreachable
The voice clip's URL answered with a status other than 200, took too long to send the whole file, or could not be fetched.
Check that the URL opens without a login or cookies, or upload the file.
invalid_callback
POST /optimize only. callback_url is not a string, or its host resolves to no address or to a private one.
Pass a public https URL.
batch_unsupported
MCP only. The request body is a JSON-RPC batch, an array of messages.
Send one JSON-RPC message per request.
invalid_parameter
seconds is not a positive number. Otherwise the engine refused our request to it, which points to a fault on our side, not in your call.
Send a video's length in seconds, or leave seconds out. For any other cause, retry once. If it keeps failing, email hello@realpng.com.
GET /optimize/{id} found no optimization with that ID on your account, or it has expired. GET /audit/{id} answers the same for a video check. GET /optimize/{id}/file and GET /optimize/{id}/image answer the same for an unknown or expired optimization, and until the optimization has succeeded.
Check the ID. Poll until its status is succeeded before you download.
unknown_job
The optimization or video check has expired. It means the same as not_found, so treat both as gone.
POST /audit or POST /optimize with image_upload_id, video_upload_id or audio_upload_id. Nothing was uploaded to that ID, the upload was already used, or it expired.
The image is over the size limit for how you sent it. image_base64 has a lower limit than image_url and uploads. Also sent when the size you give POST /uploads is over the limit, or when a JSON request body is too large, including over MCP.
The image has more pixels, width times height, than the call accepts. POST /optimize refuses it at once, never as a failed optimization. detail has the numbers.
Resize the image to fit the pixel limit and send it again. An uploaded file needs a new upload.
video_too_large
The video is over the size limit, sent by URL or as the size you give POST /uploads.
The voice clip is over the size limit, sent by URL.
Compress the clip, or send it as an MP3. See Video limits.
video_too_long
seconds is over the length limit. A video that runs past seconds, the length limit or the frame limit fails its job with the same code. detail has the numbers.
Send a shorter video, or the right seconds. See Video limits.
On POST /uploads, content_type is not a supported image, video or audio type. On a check or optimization, the file is not a supported type, is animated, or cannot be decoded. A check also answers it for a video or voice clip sent in an image field, and POST /optimize for a voice clip.
Convert the file to a supported type and send it again. Send a video as video_url or video_upload_id. An uploaded file needs a new upload.
audio_unsupported
The voice clip has no audio that can be decoded. It fails the clip's job.
Export the clip again as an MP3, WAV or M4A, then send it again.
video_unsupported
The video has no video stream that can be decoded, or no usable frame rate. It fails the video's job.
Export the video again as an MP4 with H.264, then send it again.
An optimization that fails while it runs is not an HTTP error. GET /optimize/{id} and the webhook carry its error, with the same code and message fields, as When an optimization fails shows. A video or voice check's GET /audit/{id} carries its error the same way.
Code
What it means
What to do
job_timeout
The optimization did not finish in time.
Start a new optimization.
stage1_failed
The re-render step failed.
Start a new optimization.
model_unavailable
The models could not load for this optimization.
Start a new optimization.
engine_error
The engine failed without naming the error.
Start a new optimization.
job_failed
Any other failure.
Start a new optimization.
video_too_long
The video runs past seconds, the length limit or the frame limit.
Send a shorter video, or the right seconds.
video_unsupported
The video could not be decoded.
Export it again as an MP4 with H.264.
audio_too_long
The voice clip runs past the length limit.
Send a shorter clip.
audio_unsupported
The voice clip could not be decoded.
Export it again as an MP3, WAV or M4A.
A code this table does not list means the same thing: the optimization failed. Its credits are given back. A new optimization of an uploaded file needs a new upload.
Refusals before any tool runs answer as HTTP errors in the shape above: 401 invalid_key, 402 plan_required and payment_failed, 429 rate_limited, 400 batch_unsupported, and 413 image_too_large for an oversized request body.
A request the MCP transport itself cannot read gets a JSON-RPC error object instead of that shape. That covers a missing MCP Accept header (406), a Content-Type other than JSON (415) and a body that is not a JSON-RPC message (400).
Every error inside a tool call comes back as a tool result with isError: true and the error's message as its text. The code is not included.