Skip to content

Quickstart

Go from nothing to your first uniquified variants in a few minutes. You'll generate an API key, submit a job with a source photo and your settings, poll until it finishes, and download the results — as one-time links that leave nothing behind.

This is the fastest path. For the concepts behind it, read concepts.md; for the full method list, see the API reference.

Prefer an SDK?

Skip the raw HTTP below and use the official SDKsnpm install fluidghost or pip install fluidghost — which wrap submit → poll → download (and carousels) in a few lines.


1. Get an API key

API keys are generated in the FluidGhost dashboard, not through the API.

  1. Sign in at https://ghost.fluidvip.com with an account that has a balance.
  2. Open Settings → API keys and create a key.
  3. Copy it. A key looks like fg_sk_live_.... It is shown once — store it as a secret.

Keep the key in an environment variable rather than in source:

bash
export FLUIDGHOST_KEY="fg_sk_live_8f3c..."

Running a job draws on your shared ecosystem balance. If the balance can't cover a job, calls return 402 insufficient_balance before any work runs — so you're never charged for a job that didn't happen. Top up in the dashboard. See billing.md.


2. Submit a job

You have a photo on disk and want three distinct variants, delivered as one-time links (no Drive). You send the file as a multipart upload along with your settings, and get back a jobId immediately.

The production API base is https://api-ghost.fluidvip.com/api.

bash
API="https://api-ghost.fluidvip.com/api"
KEY="$FLUIDGHOST_KEY"

curl -s -X POST "$API/jobs" \
  -H "Authorization: Bearer $KEY" \
  -F "[email protected]" \
  -F "recipeId=full-refresh" \
  -F "copies=3" \
  -F "delivery=ephemeral" \
  -F 'options={"noise":35}'

The response is a 202 with the job id and a relative status URL:

json
{ "jobId": "8f3c1a90-…", "statusUrl": "/jobs/8f3c1a90-…" }

Every copy is distinct from your source out of the box. To also guarantee no two copies resemble each other, add "ultraSafe": true to options — it bills at 2× and needs an Agency plan or higher. See Ultra Safe.

Here recipeId=full-refresh is a bundled starter preset; copies, strength, delivery, and options are convenience knobs folded into it. You can instead name a preset you saved (presetId), send a full recipeConfig for total control, or bind a default preset to your API key and omit settings entirely — see Configuring your own settings.


3. Poll until it's done

A job is asynchronous. Poll GET /jobs/{id} until status is terminal (completed, partial, or failed).

bash
curl -s "$API/jobs/8f3c1a90-…" \
  -H "Authorization: Bearer $KEY"
json
{
  "jobId": "8f3c1a90-…",
  "status": "completed",
  "progress": { "done": 3, "total": 3 },
  "variants": [
    {
      "index": 0,
      "status": "completed",
      "fileName": "IMG_4827.JPG",
      "downloadUrl": "https://api-ghost.fluidvip.com/api/jobs/8f3c1a90-…/download/0",
      "detectionRisk": 12,
      "riskBand": "low"
    },
    { "index": 1, "status": "completed", "fileName": "IMG_4828.JPG", "downloadUrl": "…/download/1", "detectionRisk": 9, "riskBand": "low" },
    { "index": 2, "status": "completed", "fileName": "IMG_4829.JPG", "downloadUrl": "…/download/2", "detectionRisk": 14, "riskBand": "low" }
  ]
}

Poll politely — every 1–2 seconds is plenty. See Rate limits.

With delivery: drive, variants carry a persistent url (and are written into your Drive) instead of a one-time downloadUrl. See Delivery modes.


4. Download the results

For ephemeral jobs, each variant has a downloadUrl. Fetch it to get the bytes. On a successful download the object is deleted — the link is one-time. A second fetch returns 410 Gone.

bash
curl -s -L "https://api-ghost.fluidvip.com/api/jobs/8f3c1a90-…/download/0" \
  -H "Authorization: Bearer $KEY" \
  -o IMG_4827.JPG

Anything you never download is purged automatically after one hour. That's the whole loop: submit → poll → download, and with ephemeral delivery nothing persists.


A complete example

python
import os, time, requests

API = "https://api-ghost.fluidvip.com/api"
KEY = os.environ["FLUIDGHOST_KEY"]
H = {"Authorization": f"Bearer {KEY}"}

# 1) submit
with open("photo.jpg", "rb") as f:
    r = requests.post(f"{API}/jobs", headers=H, files={"image": f}, data={
        "recipeId": "full-refresh",
        "copies": "3",
        "delivery": "ephemeral",
        "options": '{"noise": 35}',
    })
r.raise_for_status()
job_id = r.json()["jobId"]

# 2) poll
while True:
    s = requests.get(f"{API}/jobs/{job_id}", headers=H).json()
    if s["status"] in ("completed", "partial", "failed"):
        break
    time.sleep(1.5)

# 3) download each variant (one-time; deletes on success)
for v in s["variants"]:
    if v["status"] != "completed":
        continue
    data = requests.get(v["downloadUrl"], headers=H).content
    with open(v["fileName"], "wb") as out:
        out.write(data)

Next steps

FluidGhost API — part of the Fluidvip ecosystem.