Appearance
Official SDKs
We publish official clients so you don't have to write the HTTP, multipart, and polling yourself. Both wrap the same submit → poll → download loop and handle a single photo, a video, or a whole carousel.
| Language | Install | Package |
|---|---|---|
| TypeScript / JavaScript | npm install fluidghost | npmjs.com/package/fluidghost |
| Python | pip install fluidghost | pypi.org/project/fluidghost |
Generate an API key in the dashboard and keep it server-side.
Spoof a photo or video
run() submits a job and waits for it to finish. Photos and videos both go through the same call — the type is detected from the file. Results are one-time links: downloadVariant streams the bytes and the object is deleted on a successful download.
ts
import { FluidGhost } from "fluidghost";
import { writeFile } from "node:fs/promises";
const fg = new FluidGhost({ apiKey: process.env.FLUIDGHOST_KEY! });
const job = await fg.run({
image: { path: "photo.jpg" }, // or a video: { path: "clip.mp4" }
recipeId: "full-refresh",
copies: 3,
});
for (const v of job.variants) {
if (v.status !== "completed") continue;
const bytes = await fg.downloadVariant(job.jobId, v.index);
await writeFile(v.fileName ?? `variant-${v.index}.jpg`, bytes);
}python
import os
from fluidghost import FluidGhost
fg = FluidGhost(api_key=os.environ["FLUIDGHOST_KEY"])
job = fg.run(image="photo.jpg", recipe_id="full-refresh", copies=3) # or "clip.mp4"
for v in job["variants"]:
if v["status"] != "completed":
continue
data = fg.download_variant(job["jobId"], v["index"])
with open(v.get("fileName") or f"variant-{v['index']}.jpg", "wb") as f:
f.write(data)Spoof a carousel (matched set)
A carousel is a set of photos posted together. spoofCarousel submits one linked job per file — sharing a session and a running filename sequence so the outputs read as one cohesive post — and waits for all of them.
ts
const jobs = await fg.spoofCarousel(
[{ path: "slide1.jpg" }, { path: "slide2.jpg" }, { path: "slide3.jpg" }],
{ recipeId: "full-refresh" },
);python
jobs = fg.spoof_carousel(
["slide1.jpg", "slide2.jpg", "slide3.jpg"],
recipe_id="full-refresh",
)For a fully cohesive set (the same device across every slide), pass a recipeConfig or presetId that pins a device — otherwise each slide picks its own device family.
Errors
Every non-2xx response throws / raises a FluidGhostError mirroring the API's { error, message, details } envelope — see Errors. Handle rate_limited (back off for retryAfter) and insufficient_balance (top up in the dashboard).
What a key can do
An API key is spoof-only: submit jobs, and poll/download their results. That's the whole surface these SDKs expose. See Authentication.