> For the complete documentation index, see [llms.txt](https://bountyv.gitbook.io/vdocs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bountyv.gitbook.io/vdocs/examples.md).

# Examples

End-to-end examples in Python and Node.js.

## Python

### Install

```bash
pip install requests
```

### Generate an image

```python
import base64
import os
import requests

API_KEY = os.environ["SUBSTANCE_API_KEY"]
BASE = "https://substance-api.com/api/v1"

def generate_image(prompt: str, output_path: str, engine: str = "zimage"):
    r = requests.post(
        f"{BASE}/images/txt2img",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={"prompt": prompt, "engine": engine, "aspect": "4:5"},
        timeout=300,
    )
    r.raise_for_status()
    data = r.json()

    img = data["image"]
    raw = (
        base64.b64decode(img.split(",", 1)[1])
        if img.startswith("data:")
        else requests.get(img, timeout=120).content
    )
    with open(output_path, "wb") as f:
        f.write(raw)

    print(f"Saved to {output_path}. Balance: ${data['balance_cents'] / 100:.2f}")

generate_image("a woman on a beach at sunset, cinematic", "output.png")
```

### Generate a video (with polling)

```python
import os
import time
import requests

API_KEY = os.environ["SUBSTANCE_API_KEY"]
BASE = "https://substance-api.com/api/v1"

def generate_video(face_path: str, reference_url: str, quality: str = "pro") -> str:
    # Submit
    with open(face_path, "rb") as face:
        r = requests.post(
            f"{BASE}/videos/generate",
            headers={"Authorization": f"Bearer {API_KEY}"},
            files={"face": face},
            data={"reference_video_url": reference_url, "quality": quality},
        )
    r.raise_for_status()
    job_id = r.json()["job_id"]
    print(f"Job submitted: {job_id}")

    # Poll
    while True:
        time.sleep(8)
        r = requests.get(
            f"{BASE}/jobs/{job_id}",
            headers={"Authorization": f"Bearer {API_KEY}"},
        )
        r.raise_for_status()
        job = r.json()
        print(f"  {job['status']} — {job.get('progress', 0)}% — {job.get('current_step')}")

        if job["status"] == "completed":
            return job["video_url"]
        if job["status"] == "failed":
            raise RuntimeError(f"Job failed: {job.get('error')}")

video_url = generate_video("face.jpg", "https://www.tiktok.com/@user/video/12345")
print(f"Done: {video_url}")
```

### Check balance and top up

```python
import os
import requests

API_KEY = os.environ["SUBSTANCE_API_KEY"]
BASE = "https://substance-api.com/api/v1"

def balance_cents() -> int:
    r = requests.get(f"{BASE}/credits", headers={"Authorization": f"Bearer {API_KEY}"})
    r.raise_for_status()
    return r.json()["balance_cents"]

def topup(amount_usd: int) -> str:
    r = requests.post(
        f"{BASE}/topup",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={"amount_usd": amount_usd, "crypto_asset": "usdttrc20"},
    )
    r.raise_for_status()
    return r.json()["invoice_url"]

if balance_cents() < 1000:  # less than $10
    url = topup(100)
    print(f"Top up at: {url}")
```

## Node.js

### Generate an image

```javascript
import { writeFileSync } from "node:fs";

const API_KEY = process.env.SUBSTANCE_API_KEY;
const BASE = "https://substance-api.com/api/v1";

async function generateImage(prompt, outputPath, engine = "zimage") {
  const r = await fetch(`${BASE}/images/txt2img`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ prompt, engine, aspect: "4:5" }),
  });

  if (!r.ok) throw new Error(`${r.status}: ${await r.text()}`);
  const data = await r.json();

  const img = data.image;
  const raw = img.startsWith("data:")
    ? Buffer.from(img.split(",", 2)[1], "base64")
    : Buffer.from(await (await fetch(img)).arrayBuffer());
  writeFileSync(outputPath, raw);

  console.log(`Saved to ${outputPath}. Balance: $${(data.balance_cents / 100).toFixed(2)}`);
}

await generateImage("a woman on a beach at sunset, cinematic", "output.png");
```

### Generate a video (with polling)

```javascript
import { readFileSync } from "node:fs";

const API_KEY = process.env.SUBSTANCE_API_KEY;
const BASE = "https://substance-api.com/api/v1";

async function generateVideo(facePath, referenceUrl) {
  const form = new FormData();
  form.append("face", new Blob([readFileSync(facePath)]), "face.jpg");
  form.append("reference_video_url", referenceUrl);

  // Submit
  const sub = await fetch(`${BASE}/videos/generate`, {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body: form,
  });
  if (!sub.ok) throw new Error(`Submit failed: ${sub.status}`);
  const { job_id } = await sub.json();
  console.log(`Job submitted: ${job_id}`);

  // Poll
  while (true) {
    await new Promise(r => setTimeout(r, 8000));
    const r = await fetch(`${BASE}/jobs/${job_id}`, {
      headers: { Authorization: `Bearer ${API_KEY}` },
    });
    const job = await r.json();
    console.log(`  ${job.status} — ${job.progress || 0}% — ${job.current_step || ""}`);

    if (job.status === "completed") return job.video_url;
    if (job.status === "failed") throw new Error(`Job failed: ${job.error}`);
  }
}

const videoUrl = await generateVideo("face.jpg", "https://www.tiktok.com/@user/video/12345");
console.log(`Done: ${videoUrl}`);
```

## cURL

### Image

```bash
curl -X POST https://substance-api.com/api/v1/images/txt2img \
  -H "Authorization: Bearer $SUBSTANCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a woman on a beach at sunset, cinematic", "engine": "zimage", "aspect": "4:5"}' \
  -o response.json
```

### Video submit and poll

```bash
# submit
JOB_ID=$(curl -s -X POST https://substance-api.com/api/v1/videos/generate \
  -H "Authorization: Bearer $SUBSTANCE_API_KEY" \
  -F "face=@face.jpg" \
  -F "reference_video_url=https://www.tiktok.com/@user/video/12345" \
  | jq -r .job_id)

echo "Job ID: $JOB_ID"

# poll every 8 seconds
while true; do
  STATUS=$(curl -s https://substance-api.com/api/v1/jobs/$JOB_ID \
    -H "Authorization: Bearer $SUBSTANCE_API_KEY" | jq -r .status)
  echo "Status: $STATUS"
  if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then break; fi
  sleep 8
done
```

### Credits and topup

```bash
# balance
curl https://substance-api.com/api/v1/credits \
  -H "Authorization: Bearer $SUBSTANCE_API_KEY"

# top up $100
curl -X POST https://substance-api.com/api/v1/topup \
  -H "Authorization: Bearer $SUBSTANCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount_usd": 100, "crypto_asset": "usdttrc20"}'
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://bountyv.gitbook.io/vdocs/examples.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
