PhotoRestore

Overview

This app exposes a small, same-origin job API: upload an image, submit a restore or upscale job, then poll for its result. There are no API keys yet — every request is scoped to your browser's guest session cookie.

Base URL
/apiAll paths below are relative to this same-origin base — there is no separate API host to configure.
Authentication
Session-scoped via an httpOnly cookie issued by continuing as a guest. No API keys exist yet (see "Planned" below).

Contract guarantee: a job's result_url is always a valid image_url — you can submit it directly to /api/restore or /api/upscale to chain another operation (this is exactly how "Upscale this image" works after a Restore).

POST/api/uploads

Upload an image

Uploads a single image file and returns a same-origin asset URL you can pass as image_url to Restore or Upscale.

Request body

NameTypeRequiredBoundsDescription
filebinary (multipart/form-data)YesThe image file, sent as multipart/form-data.

Response body

NameTypeRequiredBoundsDescription
image_urlstringYesA same-origin asset URL returned by a previous upload — the only accepted image_url shape.
bytesnumberYes0–9007199254740991The stored file size in bytes.
mimestringYesThe sniffed MIME type of the stored file (authoritative — not the client-declared type).

201 response example

{
  "image_url": "/api/mock-assets/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "bytes": 2458112,
  "mime": "image/jpeg"
}

Code samples

curl -X POST 'https://your-app.example.com/api/uploads' \
  -F "file=@photo.jpg"
POST/api/restore

Create a restore job

Starts an asynchronous restore job for a previously uploaded image. Returns immediately with a pending job reference — poll GET /api/jobs/{job_id} for progress.

Request body

NameTypeRequiredBoundsDescription
image_urlstringYes/api/mock-assets/<uuid>A same-origin asset URL returned by a previous upload — the only accepted image_url shape.
user_hintstringNomax 300 charsAn optional free-text note describing the photo, used to steer the mock analysis step.
params.colorizebooleanYesWhether a faded color photo should be recolored. Black-and-white photos are never colorized.

Response body

NameTypeRequiredBoundsDescription
job_idstringYesjob_<uuid>The unique identifier for this job.
statuspending | processing | succeeded | failed | cancelledYesThe job's current lifecycle state.

202 response example

{
  "job_id": "job_3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "pending"
}

Code samples

curl -X POST 'https://your-app.example.com/api/restore' \
  -H 'Content-Type: application/json' \
  -d '{  "image_url": "/api/mock-assets/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",  "user_hint": "the sky was blue",  "params": {    "colorize": true  }}'
POST/api/upscale

Create an upscale job

Starts an asynchronous upscale job for a previously uploaded image, at the requested mode and target resolution. Returns immediately with a pending job reference.

Request body

NameTypeRequiredBoundsDescription
image_urlstringYes/api/mock-assets/<uuid>A same-origin asset URL returned by a previous upload — the only accepted image_url shape.
user_hintstringNomax 300 charsAn optional free-text note describing the photo, used to steer the mock analysis step.
params.modefast | balanced | highYesThe upscale quality/speed tradeoff.
params.target_megapixelsnumberYes1–64The desired output resolution, in megapixels.

Response body

NameTypeRequiredBoundsDescription
job_idstringYesjob_<uuid>The unique identifier for this job.
statuspending | processing | succeeded | failed | cancelledYesThe job's current lifecycle state.

202 response example

{
  "job_id": "job_3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "pending"
}

Code samples

curl -X POST 'https://your-app.example.com/api/upscale' \
  -H 'Content-Type: application/json' \
  -d '{  "image_url": "/api/mock-assets/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",  "params": {    "mode": "fast",    "target_megapixels": 12  }}'
GET/api/jobs/{job_id}

Get job status

Returns the current state of a job: status, progress, and — once succeeded — a result_url you can download or resubmit as a new image_url.

Response body

NameTypeRequiredBoundsDescription
job_idstringYesjob_<uuid>The unique identifier for this job.
typerestore | upscaleYesWhich tool created this job.
statuspending | processing | succeeded | failed | cancelledYesThe job's current lifecycle state.
progressnumberYes0–100Percent complete, 0–100.
result_urlstringNoPresent once the job succeeds — a same-origin asset URL that can also be submitted as a new image_url.
error.codeINVALID_REQUEST | UNSUPPORTED_FORMAT | FILE_TOO_LARGE | JOB_NOT_FOUND | VLM_FAILED | WORKFLOW_FAILED | TIMEOUT | CANCELLED | INTERNALYesThe machine-readable failure reason.
error.messagestringYesA human-readable failure message. Present only when status is failed.
created_atstringYesWhen the job was created, in ISO 8601.
updated_atstringYesWhen the job was last updated, in ISO 8601.

200 response example

{
  "job_id": "job_3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "type": "restore",
  "status": "succeeded",
  "progress": 100,
  "result_url": "/api/mock-assets/6c1d3a2e-4f5b-4a1e-9c2d-1a2b3c4d5e6f",
  "created_at": "2026-08-03T10:15:00.000Z",
  "updated_at": "2026-08-03T10:15:42.000Z"
}

Code samples

curl -X GET 'https://your-app.example.com/api/jobs/job_3fa85f64-5717-4562-b3fc-2c963f66afa6'
POST/api/jobs/{job_id}/cancel

Cancel a job

Requests cancellation of a job. Cancelling an already-terminal job is a no-op that returns its current, unchanged state.

Response body

NameTypeRequiredBoundsDescription
job_idstringYesjob_<uuid>The unique identifier for this job.
statuspending | processing | succeeded | failed | cancelledYesThe job's current lifecycle state.

202 response example

{
  "job_id": "job_3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "cancelled"
}

Code samples

curl -X POST 'https://your-app.example.com/api/jobs/job_3fa85f64-5717-4562-b3fc-2c963f66afa6/cancel'

Job status lifecycle

Every job moves through this state machine. pending and processing are non-terminal; succeeded, failed and cancelled are terminal and never change again.

pendingprocessingsucceededfailedcancelled

If a job doesn't reach a terminal state within the client's polling deadline, it is treated as a TIMEOUT failure.

Error codes

Every error uses this fixed set of codes. INVALID_REQUEST, UNSUPPORTED_FORMAT, FILE_TOO_LARGE and JOB_NOT_FOUND are returned as HTTP error responses; the rest surface as error.code inside a job object once that job reaches a terminal failed or cancelled state.

CodeWhere it appearsMeaning
INVALID_REQUESTHTTP 400 error responseThis request is invalid. Please check your input and try again.
UNSUPPORTED_FORMATHTTP 415 error responseThis file format isn't supported. Please use JPEG, PNG, WebP, HEIC, or HEIF.
FILE_TOO_LARGEHTTP 413 error responseThis file is too large. Please use a smaller image.
JOB_NOT_FOUNDHTTP 404 error responseWe couldn't find this job. It may have expired.
VLM_FAILEDInside a terminal job's error fieldWe couldn't analyze this image. Please try again.
WORKFLOW_FAILEDInside a terminal job's error fieldProcessing failed. Please try again.
TIMEOUTInside a terminal job's error fieldThis is taking longer than expected. Please try again.
CANCELLEDInside a terminal job's error fieldThis job was cancelled.
INTERNALHTTP 500 error responseSomething went wrong on our end. Please try again.