UNMUSH
by Rubber Raccoon

Unmush API

Developer guide · v1 · October 2026

Send the original photo and the upscaled result from any upscaler. Unmush repairs the faces the upscaler got wrong and returns the finished image, with a status for every face. Base URL: https://rubberraccoon.com/v1

Keys

Every request carries your secret key: Authorization: Bearer um_live_…. Keep it on your server — never in an app, a web page or a public repository. You make your keys yourself on your account page (a free Rubber Raccoon account is enough). A key is shown once, when it is made. If a key leaks, press "Replace key" there: the old key stops at once and its balance moves to the new one.

Test keys (um_test_…) run the whole flow for free: faces are found, jobs and callbacks work, the answer has the real shape — but nothing is repaired (the upscale comes back unchanged), nothing is charged, and would_cost tells you what a live key would have paid. Build your integration with a test key, then switch.

Quick start

A repair takes one to two minutes, so send it as a job: you get an id at once, then either poll or receive a callback.

curl https://rubberraccoon.com/v1/jobs \
-H "Authorization: Bearer $UNMUSH_KEY" \
-F original=@photo.jpg \
-F upscaled=@photo-upscaled.png \
-F callback_url=https://your-server.example/unmush-hook
→ 202 { "id": "job_8f2c…", "status": "queued", "status_url": "https://rubberraccoon.com/v1/jobs/job_8f2c…" }
curl https://rubberraccoon.com/v1/jobs/job_8f2c… -H "Authorization: Bearer $UNMUSH_KEY"
→ 200 { "id": "job_8f2c…", "status": "done", "result": { "image_url": "…", "credits_charged": 1, … } }

Send a photo — POST /v1/jobs

Multipart form data. Only the two files are required.

FieldRequiredWhat it does
originalyesThe photo before upscaling (JPEG, PNG or WebP). The faces are rebuilt from this file, so send the real original, not a resized copy.
upscaledyesThe upscaler's result for that same photo. It must show the same picture; a different or broken file is refused and not charged.
enginenoauto (default) or standard. Both repair every face with FAITHFUL, the engine that stays closest to the original. You can leave the field out.
animalsno1 also finds and repairs animal heads (pets are charged like faces).
facesnoJSON list of boxes to repair, in the original's pixels: [{"x":120,"y":80,"width":64,"height":64}]. When you send it, only these boxes are looked at (no automatic search; background faces included).
refs0 … refs47noExtra photos of one person (up to 7 per face). Faces are numbered from left to right, starting at 0. More real photos of a person make the repair surer.
callback_urlnoAn https:// address on your server that receives the finished job (see Callbacks).

Up to 48 faces per photo. Files up to 400 MB each.

Job status — GET /v1/jobs/{id}

status is queued, running, done (with result) or failed (with error). Poll every 5–10 seconds; a job only answers to the key that created it. Jobs are kept for 30 days.

The result

{
"image_url": "https://rubberraccoon.com/results/photo-….png",
"credits_charged": 1,
"balance": 249,
"message": "3 of 3 faces repaired",
"faces": [
{ "face": 1, "px": 61, "status": "repaired", "engine": "FAITHFUL", "kind": "face",
"box": { "x": 150, "y": 273, "width": 61, "height": 61 },
"detail": "Passed our checks: same position, pose and framing as in your original." }
]
}
Face statusMeaningCharged
repairedRebuilt and checked against the original.yes
kept — upscaler already faithfulLarge enough that the upscaler got it right; left exactly as delivered.no
left as deliveredNo repair passed our checks. reason says why (content_filter, pose, animal_face, engine) and detail explains it in plain words. The image shows the original's real face there, enlarged.no
skipped — background faceA small face in the background, usually out of focus. Send it in faces to repair it anyway.no

box is in the original's pixels; px is the face width there. Download the image soon after the job is done: result links are private but anyone holding a link can open it, and images are deleted after 30 days.

Callbacks

When a job finishes (or fails), we POST the same JSON you would get from GET /v1/jobs/{id} to your callback_url. Answer with any 2xx status. If your server does not answer, we try again after 30 seconds, 2 minutes and 10 minutes; polling always works as a fallback.

Every callback is signed: Unmush-Signature: t=1791264320,v1=5f2a…. The secret is the SHA-256 (hex) of your API key. Check it before you trust the body:

import crypto from "crypto";
// rawBody = the request body exactly as received (a string, before JSON parsing)
function isFromUnmush(rawBody, header, apiKey) {
const [, t, v1] = /t=(\d+),v1=([0-9a-f]+)/.exec(header || "") || [];
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;   // older than 5 minutes
const secret = crypto.createHash("sha256").update(apiKey).digest("hex");
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

Waiting for the answer — POST /v1/unmush

Same fields as /v1/jobs, but the request stays open until the image is ready (usually one to two minutes) and answers with the result directly. Use it only if your client can wait at least three minutes; otherwise use jobs.

Balance — GET /v1/balance

→ 200 { "name": "Your company", "balance": 249, "test": false }

Credits

Errors

Every error has the same shape: { "error": { "code": "…", "message": "…" } }. The message is written for people and may change; build on the code.

HTTPCodeWhat to do
400bad_requestSomething in the request is missing or wrong — the message says what (for example the two files don't show the same picture). Not charged.
400engine_not_availableYou sent an engine the API does not offer. Leave engine out.
400bad_callback_urlUse an https:// address without a user name or password.
401missing_key · invalid_keySend a valid key in the Authorization header.
402insufficient_creditsThe balance does not cover this photo. Top up; balance shows what is left. Not charged.
404not_foundNo job with this id for your key.
429rate_limited · too_many_active_jobsWait for the seconds in the Retry-After header.
503busy · engine_unavailableTemporary — try again in a minute. Not charged.
500unmush_failedOur side went wrong. Not charged; try again.
—interrupted(failed job) Our server restarted during the job. Not charged; send it again.

Limits

Your photos

Photos are used only to make the repair. We never use them to train AI models, never show them to anyone else, and delete photos and results after 30 days. You confirm that you may send the photos you send. Details: privacy · terms.

Contact

Volume and help: hello@rubberraccoon.com · Legal notice