Your first generation
Create an image through the same job API used by the Vydra workspace.
1. Create an API key
Sign in and open API Keys. Store the key when it is shown. Keep it in your server environment or secret manager; do not put it in browser JavaScript. Studio and library access requires jobs:read and jobs:write, or a matching wildcard scope.
export VYDRA_API_KEY="YOUR_API_KEY"Agents can also use POST /auth/bot-register; see authentication and OpenClaw/MCP setup.
2. Select a model and check credits
curl https://vydra.ai/api/v1/creator/models \
-H "Authorization: Bearer $VYDRA_API_KEY"
curl https://vydra.ai/api/v1/account \
-H "Authorization: Bearer $VYDRA_API_KEY"The creator catalog returns {"data":[…]}. Its estimates use default settings; changing resolution, duration, or script length changes the charge. This example uses Nano Banana at 1K for 8 credits. See model settings and prices.
3. Submit a job
curl -X POST https://vydra.ai/api/v1/jobs \
-H "Authorization: Bearer $VYDRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"workflow":"generate_image","input":{"model":"nano-banana","prompt":"An ivory dragon above a stormy sea, cinematic sunlight","resolution":"1K","aspect_ratio":"16:9"}}'A successful canonical request returns HTTP 201 with top-level id, status, and creditsCharged. It is not wrapped in data. Copy the ID, then request its result:
curl https://vydra.ai/api/v1/jobs/JOB_ID \
-H "Authorization: Bearer $VYDRA_API_KEY"Poll every 4–5 seconds while status is pending or running. On completion, read result.imageUrl, result.videoUrl, result.audioUrl, or result.text according to the workflow. On failure, inspect error and refund fields; on cancellation, stop polling.
Server-side JavaScript example
const base = "https://vydra.ai/api/v1";
const headers = { Authorization: `Bearer ${process.env.VYDRA_API_KEY}` };
async function request(path, options = {}) {
const response = await fetch(base + path, {
...options,
headers: { ...headers, ...options.headers },
signal: AbortSignal.timeout(300_000),
});
const body = await response.json();
if (!response.ok) throw new Error(JSON.stringify(body));
return body;
}
const created = await request("/jobs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
workflow: "generate_image",
input: { model: "nano-banana", resolution: "1K",
prompt: "An ivory dragon above a stormy sea", aspect_ratio: "16:9" },
}),
});
console.log("Save this job ID:", created.id);
const deadline = Date.now() + 10 * 60_000;
let result;
while (Date.now() < deadline) {
const job = await request(`/jobs/${created.id}`);
if (job.status === "completed") { result = job.result; break; }
if (["failed", "cancelled"].includes(job.status)) {
throw new Error(JSON.stringify({ status: job.status, error: job.error,
creditsRefunded: job.creditsRefunded, refundStatus: job.refundStatus }));
}
await new Promise(resolve => setTimeout(resolve, 5000));
}
if (!result) throw new Error("Polling timed out; resume with the saved job ID.");
console.log(result.imageUrl);Retry reads, not paid submissions
POST /jobs does not implement Studio run idempotency. Do not automatically resubmit it; check job history first. A polling deadline only stops your client, not the server job.Next: run a saved Studio workflow, save references, or generate speech.