AI generation API code examples
Working recipes for the FreeInd API. Each example creates a text-to-image generation and polls to completion — replace API_KEY, WORKSPACE_ID, and MODEL_ID with your values.
curl
API_KEY=fi_live_...
WORKSPACE_ID=00000000-0000-4000-8000-000000000000
MODEL_ID=00000000-0000-4000-8000-000000000001
BASE=https://free.ind.in/v1
# Create (idempotent with the header)
GENERATION=$(curl -s -X POST $BASE/generations \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: fox-1" \
-H "Content-Type: application/json" \
-d "{\"workspaceId\":\"$WORKSPACE_ID\",\"modelId\":\"$MODEL_ID\",\"params\":{\"mode\":\"text_to_image\",\"prompt\":\"a red fox in snow\"}}" \
| jq -r .id)
# Poll
while true; do
STATUS=$(curl -s "$BASE/generations/$GENERATION?workspaceId=$WORKSPACE_ID" \
-H "Authorization: Bearer $API_KEY" | jq -r .status)
echo "status: $STATUS"
[ "$STATUS" = "succeeded" ] || [ "$STATUS" = "failed" ] && break
sleep 2
doneJavaScript (Node 18+)
const API_KEY = process.env.FREEIND_API_KEY;
const WORKSPACE_ID = process.env.FREEIND_WORKSPACE_ID;
const BASE = 'https://free.ind.in/v1';
const headers = {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
};
async function api(path, init = {}) {
const res = await fetch(`${BASE}${path}`, { ...init, headers: { ...headers, ...init.headers } });
const body = await res.json();
if (!res.ok) throw new Error(`${body.error?.code}: ${body.error?.message}`);
return body;
}
const { id } = await api('/generations', {
method: 'POST',
headers: { 'Idempotency-Key': 'fox-1' },
body: JSON.stringify({
workspaceId: WORKSPACE_ID,
modelId: '00000000-0000-4000-8000-000000000001',
params: { mode: 'text_to_image', prompt: 'a red fox in snow' },
}),
});
let generation;
do {
await new Promise((r) => setTimeout(r, 2000));
generation = await api(`/generations/${id}?workspaceId=${WORKSPACE_ID}`);
console.log('status:', generation.status);
} while (['queued', 'reserved', 'submitted', 'running'].includes(generation.status));Python
import json
import time
import urllib.request
API_KEY = "fi_live_..."
WORKSPACE_ID = "00000000-0000-4000-8000-000000000000"
BASE = "https://free.ind.in/v1"
def api(path, method="GET", body=None, headers=None):
request = urllib.request.Request(
f"{BASE}{path}",
method=method,
data=json.dumps(body).encode() if body else None,
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
**(headers or {}),
},
)
with urllib.request.urlopen(request) as response:
return json.load(response)
creation = api(
"/generations",
method="POST",
headers={"Idempotency-Key": "fox-1"},
body={
"workspaceId": WORKSPACE_ID,
"modelId": "00000000-0000-4000-8000-000000000001",
"params": {"mode": "text_to_image", "prompt": "a red fox in snow"},
},
)
while True:
generation = api(f"/generations/{creation['id']}?workspaceId={WORKSPACE_ID}")
print("status:", generation["status"])
if generation["status"] in ("succeeded", "failed", "cancelled", "expired"):
break
time.sleep(2)Webhook verification (any language)
On terminal generation states, FreeInd POSTs the event to your URL signed with X-FreeInd-Signature: t=<unix-timestamp>,v1=<hmac-sha256(secret, t + "." + body)>. Verify with the signing secret shown when you created the endpoint:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifySignature(secret, rawBody, header) {
const [timestamp, signature] = header.slice(3).split(',v1=');
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}Deliveries retry with exponential backoff (up to 5 attempts) and are listed at GET /v1/webhooks/:id/deliveries. See Webhooks for the full guide.
Wallet-backed generation
Pass "paymentMode": "wallet" in the generation payload to pay from the pay-as-you-go wallet instead of plan credits:
curl -X POST https://free.ind.in/v1/generations \
-H "Authorization: Bearer fi_live_..." \
-H "Content-Type: application/json" \
-d '{
"workspaceId": "<workspace-id>",
"modelId": "<model-id>",
"paymentMode": "wallet",
"params": { "mode": "text_to_image", "prompt": "a red fox in snow" }
}'The estimated charge (credits × $0.01) is reserved before the job starts and settled on completion. Wallet payments require the workspace to have deposited the $10 minimum — see Billing & wallet.
More
Input files (image/video modes)
For image_to_image, image_to_video, and video_to_video modes, the source file goes in inputs either way:
Option 1 — upload (private): request a presigned URL with POST /v1/files/upload-url, PUT the bytes, finalize with POST /v1/files/upload-complete, then pass the asset ids in inputs.imageAssetIds / inputs.videoAssetIds.
Option 2 — direct URL (no upload): pass a public http(s) URL or a base64 data URL in inputs.imageUrls / inputs.videoUrls. Data URLs must declare a matching mime type (data:image/… for image modes, data:video/… for video-to-video); http(s) targets must be publicly reachable (private network addresses are rejected). See the API reference.
Note
All examples here are also maintained in the repo under docs/api/examples.md.