Skip to content

Core concepts

This page is the deeper tour: jobs, sources, settings, variants, delivery modes, and the detection-risk audit. Read it once and the reference pages become a lookup rather than a puzzle.

Jobs

A job is one uniquify run: one source in, copies variants out. Jobs are asynchronous — uniquifying a batch (especially video) is real compute — so submitting a job returns a jobId immediately, and you poll for the result.

A job moves through a lifecycle:

StatusMeaning
queuedAccepted and waiting for a worker.
processingA worker picked it up; variants are being produced.
completedEvery variant succeeded.
partialThe job finished but only some variants succeeded.
failedThe job failed wholesale, or every variant failed.

completed, partial, and failed are terminal — poll until you see one of them. A job also carries a progress counter ({ done, total }) so you can show a progress bar.

Ultra Safe early stop. With Ultra Safe on, a batch reseeds each copy until it's perceptually distinct from its siblings. If too many copies in a row can't be separated, a circuit breaker stops the batch early: progress.stoppedEarly is true, done (< total) valid variants were produced, and stoppedReason explains it. This is a feature, not an error — the copies you got are all distinct. See Ultra Safe.

Sources

The source is the media you uniquify. Supply it one of two ways:

  • Multipart upload — send the bytes as an image field on POST /jobs.
  • Pre-uploaded key — upload once via a presigned URL, then reference it as inputKey on the job (useful for large videos or reusing one source across jobs).

Supported formats, detected by file signature (not the filename or client MIME, both spoofable):

KindFormats
ImageJPEG, PNG, WebP, TIFF, HEIC, AVIF
VideoMP4, MOV

Anything else is rejected at the boundary with 400 bad_request before a byte is stored. The upload cap is 100 MB; oversized uploads return 413 payload_too_large.

Settings

Settings describe how to uniquify and how many variants to make. A job's recipe comes from exactly one place, and the knobs adjust it:

  • Starter preset — name a bundled recipe with recipeId. The fast path.
  • Saved preset — name one of your own with presetId, or bind it to an API key so calls need no settings at all.
  • Full recipe — send a complete recipeConfig: the entire node graph the Studio builds (device, place, time, exposure, noise, warp, crop, audio, output format, and more). Total control.

The API takes the first that is present: inline recipeConfig/recipeId, then presetId, then your key's default preset. Sending both recipeId and recipeConfig is a 400; so is resolving none of the three. The knobs fold on top of whichever won. Every resolved recipe is validated server-side; an invalid one returns 400 bad_request with rule-cited findings.

The full settings surface, the starter recipes, and worked examples are in Configuring your own settings. The live option vocabulary (device presets, city presets, filter/noise names, limits) is available at runtime from GET /options.

Convenience knobs

KnobTypeEffect
copiesinteger ≥ 1How many variants to produce. Over the limit (100000) is a 400, not a clamp.
strengthinteger 0–100Default intensity for transforms that don't set their own.
deliverydrive | ephemeralWhere results go (see below).
optionsobjectShallow-merged onto the recipe's global_settings — e.g. ultraSafe, noise, carousel settings.

Ultra Safe

Every variant is distinct from the source by default. Ultra Safe makes each copy distinct from its siblings as well: the worker scores each new copy against every copy already accepted in the batch and re-rolls the ones that land too close. Turn it on with ultraSafe: true in options or in a recipe's global_settings.

Three things follow from re-rolling, and all three are the point rather than side effects:

  • It is billed at 2× the per-photo rate on your plan — a delivered file may have been rendered several times. Videos are never surcharged; the video path has its own runner and ignores the flag.
  • A run is no longer byte-reproducible. A locked seed no longer reproduces the same files, which is why the exact-count path (no re-rolls) exists alongside it.
  • The batch may stop early. If too many copies in a row can't be separated, a circuit breaker halts the run: progress.stoppedEarly is true, stoppedReason says why, and you are billed only for what was delivered.

It works best up to about 15 copies per photo. Beyond that the breaker is increasingly likely to stop the run before the count you asked for.

Ultra Safe is included on the Agency plan and higher. On any other plan:

  • POST /jobs/quote prices the run without it (the plain rate, ultraSafe: false) and answers ultraSafeAllowed: false;
  • POST /jobs refuses a recipe that asks for it with 403 forbidden, and refuses it before anything is uploaded or charged.

The job is refused rather than quietly run without the feature: Ultra Safe is a promise about the output, and silently handing back near-twins is the outcome it exists to prevent. Check GET /mefeatures.ultraSafe if you want to branch before submitting.

Variants

A variant is one produced result. Each variant is generated from its own seed, so no two variants in a batch are alike — different pixels, different metadata, a different device-authentic filename.

A completed variant carries:

  • index — its position in the batch (0-based),
  • fileName — a device-authentic download name matching the spoofed make/model (e.g. IMG_4827.HEIC, PXL_20251222_225202463.jpg),
  • a result pointer — either a persistent url (drive delivery) or a one-time downloadUrl (ephemeral delivery),
  • an optional detection-risk audit (see below),
  • for a video, an optional browser-playable previewUrl; for a Live Photo, the paired .MOV/.mp4 companions and their shared contentIdentifier.

If a single variant fails, its status is failed and it carries an error — the rest of the batch can still succeed (partial).

Delivery modes

Every job chooses where its results go, via the delivery knob:

drive (default for the web app)

Each variant is written into your FluidGhost Drive, in a per-session folder, exactly as the Studio does. Variants carry a persistent url. Results stay until you delete them. Use this when you want your results archived and browsable.

ephemeral (one-time, no Drive)

Results are not saved to your Drive. Instead each variant gets a one-time download link (downloadUrl) served by the API:

  • The link deletes its object on the first successful download. A completed download is the trigger; an interrupted/dropped download does not delete, so you can retry.
  • A second download of an already-consumed variant returns 410 Gone.
  • Multi-copy jobs return one link per variant — each deletes on its own download.
  • Anything never downloaded is purged after one hour (a hard fallback so nothing lingers).

Use this for "uniquify it, grab it, and leave nothing behind." Full details and the interrupted-download semantics are in One-time results.

Detection-risk audit

Optionally, each variant carries a detection-risk measurement: how similar the result still is to the original, on a 0–100 scale where higher = more detectable.

  • detectionRisk — the headline score (0–100).
  • riskBand — a coarse low / medium / high.
  • riskAudit — the per-hash distances behind the score (PDQ, dHash, pHash, pixel MAD, and for video the frames sampled + mean/worst-frame risk).
  • metadataRisk — lingering metadata tells (e.g. EXIF/GPS/MakerNotes that survived), with a severity and a list of tells.

Pre-calibration, treat the score as a relative distinctness measure, not a guarantee. It's there so you can tell, per variant, how far each result moved from the source.

Account & balance

Your account has one shared ecosystem balance — the same one FluidTalk conversations spend. Jobs debit it per variant before running and refund on failure. Read it any time with GET /me, and top up in the dashboard. See Billing.

FluidGhost API — part of the Fluidvip ecosystem.