API reference

Build with Snap Fix

Send an image and a plain-language instruction, and get back a validated, production-ready file. This page documents every endpoint, header, and error code — what's paid, and what's always free.

BASE URLhttps://api.snapfix.mediaor http://localhost:8787 for local development

01 Getting started

Overview

Snap Fix is an outcome-oriented image transformation API. A client sends an image plus a natural-language instruction — “crop into a circle,” “keep it under 5 MB” — and Snap Fix plans the processing steps, runs them, and re-verifies the real output against every measurable constraint before returning it. Payment only settles on a verified success.

MethodPathDescription
POST/v1/transformTransform an image from an instruction. x402-paid.
GET/v1/jobs/:idGet a job's status and metadata.
GET/v1/jobs/:id/downloadDownload a completed job's output.

02 Getting started

Authentication (x402)

POST /v1/transform is protected by the x402 protocol — no API key, no account, no subscription. Pricing, network, and facilitator are configured server-side, kept separate from the transformation logic.

Nothing is charged unless the transformation actually succeeds and passes constraint verification. The gate verifies your payment header (checks you can pay — no funds move) before your request is processed; it only settles the real on-chain charge after a successful (< 400) response. Validation failures, ambiguous or unsupported instructions, execution errors, and unmet constraints are all free. The API negotiates x402 version 2; the v1 X-PAYMENT header is accepted as a fallback.

POST /v1/transform UNPAID
REQUEST
curl -i -X POST https://api.snapfix.media/v1/transform \
  -F "instruction=convert to jpeg" \
  -F "file=@photo.png"
RESPONSE · 402 PAYMENT REQUIRED
PAYMENT-REQUIRED: <base64-encoded JSON>

// decoded, the header carries the payment requirements:
{
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:8453",
    "maxAmountRequired": "50000",
    "resource": "https://api.snapfix.media/v1/transform",
    "payTo": "0x...",
    "asset": "0x...",
    "description": "Outcome-verified image transformation"
  }]
}

Sign a payment authorization for those requirements and retry the identical request with a PAYMENT-SIGNATURE header (an x402-aware client does this for you). A valid payment authorization lets it through to processing — it's only settled once the response succeeds, and the settlement receipt (with the on-chain transaction hash) comes back in the PAYMENT-RESPONSE response header.

POST /v1/transform PAID RETRY
REQUEST
curl -i -X POST https://api.snapfix.media/v1/transform \
  -H "PAYMENT-SIGNATURE: <base64-encoded payment payload>" \
  -H "Idempotency-Key: 3f29f6c2-...-a-client-generated-uuid" \
  -F "instruction=convert to jpeg" \
  -F "file=@photo.png"

Any x402-aware client works the same way — the snippets above illustrate the shape of the call; check your chosen library's current docs for exact function names.

03 Making a request

Transform an image

Two input modes:

Multipart upload (multipart/form-data) — shown above:

FieldRequiredDescription
fileYesThe image — jpeg, png, webp, gif, tiff, avif — detected from the actual bytes, not the filename.
instructionYesNatural-language description of the desired outcome.

JSON + remote URL (application/json):

POST /v1/transform JSON
REQUEST BODY
{
  "instruction": "resize to 1200x630 and convert to webp",
  "imageUrl": "https://example.com/photo.jpg"
}

The remote URL is fetched with SSRF defense-in-depth: https-only, private/loopback/link-local/reserved IP literals rejected, redirects re-validated per hop (capped), streaming size cap, fetch timeout.

Optional headers:

HeaderDescription
Idempotency-KeySee Idempotency below. Strongly recommended.
Accept: application/jsonReturns full JSON metadata + a download link instead of the binary. See Success responses.

Optional query param ?mode=async plans synchronously — so a rejected instruction still returns its real error immediately and is never charged as a false 202 — then returns 202 Accepted with a Link to GET /v1/jobs/:id for polling, instead of blocking for the result:

POST /v1/transform?mode=async
REQUEST
curl -s -X POST "https://api.snapfix.media/v1/transform?mode=async" \
  -H "PAYMENT-SIGNATURE: <payment>" \
  -F "instruction=convert to jpeg" -F "file=@photo.png"
# -> 202 {"jobId":"3f29f6c2-...","status":"processing"}

curl -s https://api.snapfix.media/v1/jobs/3f29f6c2-...
# poll until "status" is "completed" (or "failed"), then:
curl -sO https://api.snapfix.media/v1/jobs/3f29f6c2-.../download

Images are fast enough that this is rarely needed — the seam exists for future, slower media types.

04 Making a request

Idempotency

Send an Idempotency-Key header — any client-generated unique string — with every request you might need to retry.

Retry lands on…Result
A completed job, same keyThe cached result is returned immediately, without re-entering the payment gate at all. A true retry can never charge you twice.
A job still processing (within a short staleness window)409 Conflict with Retry-After and a Link to GET /v1/jobs/:id.
A failed or rejected jobReclaimed and reprocessed — a prior failed attempt was never charged, so this is a normal, free retry.
No Idempotency-Key headerNo dedup — each request is independent. A client responsibility, not a server-enforced requirement.

05 Making a request

Success responses

Default — the binary, directly:

RESPONSE 200 OK
HEADERS
Content-Type: image/jpeg
Content-Disposition: attachment; filename="snapfix-<job-id>.jpeg"
X-Job-Id: 3f29f6c2-...
X-Snapfix-Width: 1200
X-Snapfix-Height: 630
X-Snapfix-Format: jpeg
Link: </v1/jobs/3f29f6c2-...>; rel="metadata"

<binary bytes>

With Accept: application/json — full metadata + a download reference instead of inline binary:

RESPONSE 200 OK JSON
BODY
{
  "jobId": "3f29f6c2-...",
  "status": "completed",
  "instruction": "resize to 1200x630, convert to webp, keep under 500KB",
  "plan": {
    "status": "ok",
    "operations": [
      { "op": "resize", "width": 1200, "height": 630, "fit": "cover" },
      { "op": "convertFormat", "format": "webp" }
    ],
    "constraints": { "exactWidth": 1200, "exactHeight": 630, "format": "webp", "maxBytes": 500000, "forbidUpscaling": true }
  },
  "validation": {
    "overallPassed": true,
    "checks": [
      { "constraint": "exactWidth", "required": 1200, "measured": 1200, "passed": true },
      { "constraint": "exactHeight", "required": 630, "measured": 630, "passed": true },
      { "constraint": "format", "required": "webp", "measured": "webp", "passed": true },
      { "constraint": "maxBytes", "required": 500000, "measured": 421887, "passed": true }
    ],
    "iterations": 3
  },
  "output": {
    "contentType": "image/webp",
    "format": "webp",
    "byteSize": 421887,
    "width": 1200,
    "height": 630,
    "hasAlpha": false,
    "filename": "snapfix-3f29f6c2-....webp"
  },
  "downloadUrl": "/v1/jobs/3f29f6c2-.../download"
}

06 Making a request

Error responses

Every failure is a structured { "error": { "code", "message", "unmetRequirement"? } } body. None of these are charged.

StatusWhenExample error.code
400Malformed request — bad multipart, bad JSON, missing fields, SSRF-blocked/unreachable remote URLinvalid_request, remote_fetch_blocked
402No or invalid paymentx402-native 402 body, not the error envelope above
409An Idempotency-Key retry landed while the original request is still processingalready_processing
413/415Input too large, or not a recognized image formatinput_too_large, unsupported_media_type
422The planner rejected the instruction, or the output failed a measurable constraint after iterationambiguous, unsupported_operation, unsafe, impossible_constraints, unclear_target, input_mismatch, constraint_unmet
500/502Internal error, or the container failed unexpectedlyinternal_error, execution_failed
504The container exceeded its execution time limitexecution_timeout
RESPONSE 422 AMBIGUOUS
BODY
{
  "error": {
    "code": "ambiguous",
    "message": "The instruction doesn't specify what outcome is wanted.",
    "unmetRequirement": null
  }
}
RESPONSE 422 CONSTRAINT UNMET
BODY
{
  "error": { "code": "constraint_unmet", "message": "Output did not satisfy all requested constraints after iteration" },
  "validation": {
    "overallPassed": false,
    "checks": [
      { "constraint": "maxBytes", "required": 5000, "measured": 41230, "passed": false }
    ]
  }
}

A constraint that can't be satisfied even after iteration is reported this way — never returned as a silently oversized file.

07 Jobs

Get job status

GET /v1/jobs/:id — metadata for any job, queryable regardless of status:

RESPONSE 200 OK
BODY
{
  "jobId": "3f29f6c2-...",
  "status": "completed",
  "instruction": "convert to jpeg",
  "plan": { "...": "..." },
  "validation": { "...": "..." },
  "error": null,
  "createdAt": "2026-01-01T00:00:00.000Z",
  "completedAt": "2026-01-01T00:00:02.000Z",
  "downloadUrl": "/v1/jobs/3f29f6c2-.../download"
}

404 (job_not_found) if the id doesn't exist.

08 Jobs

Download output

GET /v1/jobs/:id/download streams the output binary for a completed job. 410 Gone (result_expired) once the R2 lifecycle TTL has removed it — outputs are short-lived by design. See Limits.

09 Reference

Limits

LimitDefaultConfigured via
Max input size25 MBMAX_INPUT_BYTES
Max output size25 MBMAX_OUTPUT_BYTES
Max input pixels (decompression-bomb guard)50 MPMAX_INPUT_PIXELS
Container execution timeout45sCONTAINER_EXEC_TIMEOUT_MS
Remote URL fetch timeout10sREMOTE_FETCH_TIMEOUT_MS
Remote URL max redirects5REMOTE_FETCH_MAX_REDIRECTS
Result retention (R2 TTL)1 hourJOB_RESULT_TTL_SECONDS
Idempotency staleness window90sIDEMPOTENCY_STALE_MS

10 Reference

Supported operations

The planner chooses from these 25 operations to satisfy an instruction:

OperationNotes
resizeWidth/height/fit; respects forbidUpscaling (default true)
convertFormatjpeg, png, webp, avif
cropExplicit x/y/width/height rectangle, or per-edge pixel insets
cropToAspectRatioCrop (never stretch) to a target aspect ratio
cropCircleCentered circle crop; requires an alpha-capable target format
rotateArbitrary degrees
autoOrientApplies EXIF orientation, then drops it
removeBackgroundSelf-hosted ONNX (u2netp) — no third-party API call
trimRemoves a uniform-color border
stripMetadataExplicit no-op marker — metadata is stripped by default on every operation
compressThe target for maxBytes iteration
adjustBrightness/contrast/saturation (multipliers around 1.0) and gamma (1.0–3.0)
grayscaleConvert to grayscale
invertInvert colors
sepiaSepia tone
normalizeAuto-levels / contrast stretch
blurGaussian blur (whole image)
sharpenSharpening (whole image)
flattenBackgroundFlatten transparency onto a solid background color
thresholdPure black/white at a cutoff (1–254, default 128)
padExtends the canvas to an aspectRatio or explicit dimensions, filling new area with background — never crops
borderSolid-color border of a given width on every side
flipMirror horizontally or vertically
scaleResize by a relative factor (e.g. 0.5, 2) — resolved against real decoded dimensions, container-side
inspectRead-only metadata report; no pixel changes. See below.
roundedCornersRounds the image's corners; requires an alpha-capable target format
watermarkTextOverlays semi-transparent text at a position/opacity

Measurable constraints (maxBytes, exactWidth/exactHeight, maxWidth/maxHeight, format, requireTransparency, forbidUpscaling, preserveAnimation) are always re-verified against the real output. Subjective preferences (qualityHint, free-text notes) inform defaults but are never a pass/fail gate — there's no objective check for "high quality."

11 Reference

Read-only inspection

inspect doesn't transform pixels — its findings are returned as an inspection object alongside the normal output/validation fields, with Accept: application/json:

RESPONSE 200 OK JSON
BODY
{
  "inspection": {
    "width": 1200, "height": 800, "format": "jpeg", "hasAlpha": false,
    "hasExif": true, "hasIccProfile": false, "isAnimated": false,
    "hasGpsData": false
  }
}

12 Reference

Not supported (by design, this pass)

The planner rejects these with unsupported_operation rather than attempting them:

  • AI upscaling / super-resolution
  • Generative edits — inpainting, outpainting, generating new content, AI deblurring
  • OCR / text extraction
  • PDF rendering
  • Camera RAW processing
  • Barcode / QR decode
  • Face / object / subject detection
  • Any operation requiring a second input image — compositing, logo overlay, image-as-mask
  • Multi-file / bundle output — icon packs, zips, batched variants in one response
  • Real ICC-managed print color conversion
  • Per-frame animation editing — frame-rate change, reordering, time-based trimming — beyond simple format-preserving passthrough via preserveAnimation