Unmush API
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.
| Field | Required | What it does |
|---|---|---|
original | yes | The photo before upscaling (JPEG, PNG or WebP). The faces are rebuilt from this file, so send the real original, not a resized copy. |
upscaled | yes | The upscaler's result for that same photo. It must show the same picture; a different or broken file is refused and not charged. |
engine | no | auto (default) or standard. Both repair every face with FAITHFUL, the engine that stays closest to the original. You can leave the field out. |
animals | no | 1 also finds and repairs animal heads (pets are charged like faces). |
faces | no | JSON 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 … refs47 | no | Extra 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_url | no | An 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 status | Meaning | Charged |
|---|---|---|
repaired | Rebuilt and checked against the original. | yes |
kept — upscaler already faithful | Large enough that the upscaler got it right; left exactly as delivered. | no |
left as delivered | No 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 face | A 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
- $0.10 per credit, pay as you go, no monthly minimum. One credit is one image with up to 4 repaired faces.
- 1 credit per started group of 4 repaired faces or animals (1–4 faces = 1 credit, 5–8 = 2 …).
- Faces narrower than 25 px in the original are left as delivered: too little of the real face is left to rebuild it faithfully. Send
tiny_ok=1to repair them anyway, knowing the result can show a different person. - Kept faces, background faces and faces that did not pass our checks are free. A photo with no repaired face costs nothing.
- When a job starts we reserve 1 credit; once the faces are found we reserve the full price and settle to the real charge when it finishes. A photo with more faces than your balance covers is refused before any work (
insufficient_credits) and costs nothing; a failed job is refunded in full. - Each answer shows
credits_chargedand your remainingbalance. - API credits sit on your key, in a balance of their own: they are not the credits of the web app's wallet, and the two cannot be moved between each other. Top-ups start at $20 (200 credits) and do not expire; you buy them on your account page in steps of $20, $50 or $100. For more than about 10,000 credits a month, write to hello@rubberraccoon.com.
Errors
Every error has the same shape: { "error": { "code": "…", "message": "…" } }. The message is written for people and may change; build on the code.
| HTTP | Code | What to do |
|---|---|---|
| 400 | bad_request | Something in the request is missing or wrong — the message says what (for example the two files don't show the same picture). Not charged. |
| 400 | engine_not_available | You sent an engine the API does not offer. Leave engine out. |
| 400 | bad_callback_url | Use an https:// address without a user name or password. |
| 401 | missing_key · invalid_key | Send a valid key in the Authorization header. |
| 402 | insufficient_credits | The balance does not cover this photo. Top up; balance shows what is left. Not charged. |
| 404 | not_found | No job with this id for your key. |
| 429 | rate_limited · too_many_active_jobs | Wait for the seconds in the Retry-After header. |
| 503 | busy · engine_unavailable | Temporary — try again in a minute. Not charged. |
| 500 | unmush_failed | Our side went wrong. Not charged; try again. |
| — | interrupted | (failed job) Our server restarted during the job. Not charged; send it again. |
Limits
- 60 new jobs per minute and 10 queued or running jobs per key. Need more? Ask us.
- 48 faces per photo; files up to 400 MB each.
- Photos and results are deleted after 30 days, job records too.
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