Appearance
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 SDKs — npm 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.
- Sign in at https://ghost.fluidvip.com with an account that has a balance.
- Open Settings → API keys and create a key.
- 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_balancebefore 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": truetooptions— 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 persistenturl(and are written into your Drive) instead of a one-timedownloadUrl. 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.JPGAnything 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
- Authentication — key formats, headers, rotation.
- Configuring your own settings — presets, knobs, and the full recipe graph.
- One-time results — the delete-on-download flow in depth.
- API reference — every endpoint and field.