Imagera AI - AI content creation platform for generating images, cloning voices, creating avatars, and enhancing videos. Privacy Policy | Terms

Imagera Speech — Voice Capture API

Extracts a voice embedding from an audio source for later synthesis.

imagera-speech-voice-captureAudio generationaudio0 credits

Calling the API

Install the client

Nothing to install. The API is plain HTTPS and JSON, so the HTTP client already in your project is the client — fetch, requests, curl. There is no SDK.

Setup your API key

Keys are shown once. Keep yours in an environment variable — never in source control, never in client-side code.

Create an API key
bashEnvironment
# Put the key in your environment, never in source control.
export IMAGERA_API_KEY="ima_sk_YOUR_KEY"

Submit a request

Name the model in the path, send its parameters as the JSON body. The call returns a request_id immediately; generation continues in the background.

javascriptPOST /v1/queue/imagera-speech-voice-capture
const response = await fetch(
  "https://api.imagera.ai/v1/queue/imagera-speech-voice-capture",
  {
    method: "POST",
    headers: {
      "Authorization": `Key ${process.env.IMAGERA_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      "audio": "https://example.com/input.mp3"
    }),
  },
);

// 202 Accepted — the job is queued, not finished.
const job = await response.json();
console.log(job.request_id, job.status);

Authentication

API key

Every request carries your key in the Authorization header. Use the Key scheme.

httpRequest header
Authorization: Key ima_sk_YOUR_KEY

Bearer is accepted as well, so an HTTP client that only speaks that scheme works unchanged — it is the same key, not a different credential. Scheme matching is case-insensitive.

Keys are revocable per integration and stop working within about a minute. Rotate by creating the new key, deploying it, then revoking the old one.

Pricing

0 credits per request, whatever parameters you send.

Webhooks

Webhooks are the intended way to learn that a generation finished: you submit, we call your server when it is done, and you never hold a connection open or run a polling loop.

Register an endpoint

Endpoints are registered against your account, not passed per request, so a leaked request body cannot redirect your callbacks. Each gets its own signing secret, shown once.

  • • HTTPS only — plaintext http:// is refused.
  • • Must resolve to public internet space. Private, loopback, link-local and cloud metadata addresses are refused.
  • • Ports 443 and 8443.
  • • Redirects are followed up to 3 hops, and every hop is re-validated.
  • • No credentials in the URL.
jsonPOST your endpoint — Imagera-Webhook-Event-Type: generation.completed
{
  "event_id": "evt_7c1f9a3e5b2d4086a1c3e5f7b9d0",
  "event_type": "generation.completed",
  "api_version": "2026-07-31",
  "created_at": "2026-07-31T08:42:17.000Z",
  "request_id": "req_8f3c1a9e2b7d",
  "status": "OK",
  "data": {
    "request_id": "req_8f3c1a9e2b7d",
    "gateway_request_id": "req_8f3c1a9e2b7d",
    "status": "COMPLETED",
    "model": "imagera-speech-voice-capture",
    "credits_used": 0,
    "timings": {
      "inference_seconds": 4.2
    },
    "audio": [
      {
        "url": "https://example.com/output.mp3",
        "file_name": "output.mp3",
        "content_type": "audio/mpeg"
      }
    ],
    "output_count": 1
  },
  "error": null
}

Verify the signature

Verify every delivery before you trust it. The body hash is inside the signed string, so a valid signature proves both the sender and the exact bytes.

Use the raw request bytes. Re-serialising parsed JSON changes key order and whitespace and will never reproduce the hash. This is the most common integration failure.
javascriptVerify a delivery
const crypto = require("node:crypto");

function verifyImageraWebhook(rawBody, headers, secrets, toleranceSeconds = 300) {
  const eventId   = headers["imagera-webhook-id"];
  const timestamp = headers["imagera-webhook-timestamp"];
  const header    = headers["imagera-webhook-signature"];
  if (!eventId || !timestamp || !header) return false;

  // Replay bound. Without it a captured request is valid forever.
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > toleranceSeconds) return false;

  const bodyHash     = crypto.createHash("sha256").update(rawBody).digest("hex");
  const signedString = `v1\n${eventId}\n${timestamp}\n${bodyHash}`;

  // The header may carry SEVERAL signatures during a secret rotation.
  const candidates = header.split(/\s+/)
    .filter((p) => p.startsWith("v1="))
    .map((p) => Buffer.from(p.slice(3), "hex"));

  return [].concat(secrets).some((secret) => {
    const expected = crypto.createHmac("sha256", secret).update(signedString).digest();
    return candidates.some(
      (c) => c.length === expected.length && crypto.timingSafeEqual(c, expected),
    );
  });
}

// Express — note express.raw(), NOT express.json().
app.post("/imagera", express.raw({ type: "application/json" }), (req, res) => {
  if (!verifyImageraWebhook(req.body, req.headers, [process.env.IMAGERA_WEBHOOK_SECRET])) {
    return res.status(401).send("bad signature");
  }
  const event = JSON.parse(req.body.toString("utf8"));
  // Dedupe on event.event_id, then acknowledge fast.
  res.status(200).send("ok");
});

Retries and delivery

Delivery is at-least-once, so the same event can arrive twice. Record the event_id values you have processed and ignore repeats — it is stable across every attempt.

Return 2xx as soon as the event is durably queued and do the work afterwards; a handler slower than 10s is retried unnecessarily. Failed attempts back off 1s → 2s → 4s → 8s → 16s, up to 6 attempts. A 4xx other than 408, 425 or 429 is permanent and is not retried.

Reject any delivery whose Imagera-Webhook-Timestamp is more than 300 seconds from your clock — that is the replay bound.

Queue

Every generation is queued. These endpoints are how you reconcile state — after a missed webhook, on restart, or to cancel. Poll sparingly; prefer the webhook.

Submit a request

POST /v1/queue/imagera-speech-voice-capture returns 202 Accepted. Follow the URLs it gives you rather than rebuilding them.

json202 Accepted
{
  "request_id": "req_8f3c1a9e2b7d",
  "status": "IN_QUEUE",
  "model": "imagera-speech-voice-capture",
  "status_url": "https://api.imagera.ai/v1/queue/requests/req_8f3c1a9e2b7d/status",
  "response_url": "https://api.imagera.ai/v1/queue/requests/req_8f3c1a9e2b7d",
  "cancel_url": "https://api.imagera.ai/v1/queue/requests/req_8f3c1a9e2b7d/cancel"
}

Fetch request status

Status is one of IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELLED. The last three are terminal — stop asking once you see one.

javascript/v1/queue/requests/{id}/status
// Prefer the status_url the submit response gave you.
const res = await fetch(job.status_url, {
  headers: { "Authorization": `Key ${process.env.IMAGERA_API_KEY}` },
});
const { status } = await res.json();

Get the result

Once the status is COMPLETED, fetch the result from response_url. Errors come back as application/problem+json documents with a machine-readable code, so you can branch on the code rather than on message text.

javascript/v1/queue/requests/{id}
const res = await fetch(job.response_url, {
  headers: { "Authorization": `Key ${process.env.IMAGERA_API_KEY}` },
});
const result = await res.json();

Files

Anywhere the schema names a URI you can pass either an inline data URI or a public URL. There is no upload endpoint to call first, and generated outputs come back as URLs you can feed straight into a follow-up request.

Data URI (base64)

Simplest for small inputs. Base64 inflates the payload by about a third, so prefer a URL for anything large.

javascriptInline upload
import { readFile } from "node:fs/promises";

const bytes = await readFile("./input.jpg");
const dataUri = `data:image/jpeg;base64,${bytes.toString("base64")}`;

// Pass it anywhere the schema expects a URI.
body: JSON.stringify({ audio: dataUri })

Hosted files (URL)

Any publicly reachable HTTPS URL. It must be readable without authentication — a signed URL works, a private bucket does not.

javascriptHosted input
body: JSON.stringify({"audio":"https://example.com/input.mp3"})

Uploading files

Pass a data URI for small files, or host the file yourself and pass its URL. Nothing else is required.

Schema

Input

audiostring (uri)required

Audio source to extract voice embedding from

processing_speedstringoptional

Delivery tier. "ultra-fast" routes to the accelerated path and is priced separately (see the `speed` column of this model's price table).

Options: standard, ultra-fast

Default "standard"

Minimal example

jsonRequired only
{
  "audio": "https://example.com/input.mp3"
}

Full example

jsonAll parameters
{
  "audio": "https://example.com/input.mp3",
  "processing_speed": "standard"
}

Output

Returns audio as URLs, not inline bytes. On the result response they arrive at the top level under audio, alongside request_id, status, model, credits_used and timings.

A webhook delivery wraps that same object one level down, in the event's data field, and adds output_count — so event.data.audio on a callback is result.audio when you fetch it yourself. Only the webhook has a data field.

The output payload is not published as a formal schema, so it is not documented field-by-field here rather than being guessed at. Submit one request and inspect the response to see its exact shape.