> ## Documentation Index
> Fetch the complete documentation index at: https://docs.finesse.flamapp.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Get a job

> Poll the status of an async clone/enroll job.

Returns the status of an asynchronous clone job started by [`/clone`](/voice-api/clone-voice) or
[`/enroll`](/voice-api/enroll-voice). Poll this endpoint until `status` reaches a terminal state. The
job must belong to the API key's workspace, otherwise the request returns `404`.

#### Authorization

<ParamField header="X-API-Key" type="string" required>
  Your workspace API key (`flv_…`).
</ParamField>

#### Path parameters

<ParamField path="id" type="string" required>
  The `job_id` returned by `/clone` or `/enroll`.
</ParamField>

#### Response

<ResponseField name="data.id" type="string">
  The job id.
</ResponseField>

<ResponseField name="data.voice_id" type="string">
  The voice this job is enrolling.
</ResponseField>

<ResponseField name="data.job_type" type="string">
  The kind of job, e.g. `instant_clone`, `pro_train`, `verify`, `localize`, `design_preview`,
  `reembed`.
</ResponseField>

<ResponseField name="data.status" type="string">
  Job state. `pending` or `running` while in progress; terminal states are `succeeded`, `failed`,
  `cancelled`, and `expired`. When `succeeded`, the voice's status is `ready`.
</ResponseField>

<ResponseField name="data.progress" type="integer">
  Completion percentage, `0`–`100`.
</ResponseField>

<ResponseField name="data.error" type="object">
  Present when `status` is `failed` - carries the provider error detail. A reference clip the cloner
  can't use (it needs at least \~6 seconds of continuous speech) fails the job this way.
</ResponseField>

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/jobs/665f1a2b3c4d5e6f7a8b9aa01" \
    -H "X-API-Key: $FLAM_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const jobId = "665f1a2b3c4d5e6f7a8b9aa01";
  const res = await fetch(
    `https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/jobs/${jobId}`,
    { headers: { "X-API-Key": process.env.FLAM_API_KEY } },
  );
  const { data } = await res.json();
  console.log(data.status, data.progress);
  ```

  ```python Python theme={null}
  import os, requests

  job_id = "665f1a2b3c4d5e6f7a8b9aa01"
  res = requests.get(
      f"https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/jobs/{job_id}",
      headers={"X-API-Key": os.environ["FLAM_API_KEY"]},
  )
  print(res.json()["data"]["status"])
  ```
</CodeGroup>

```json Response theme={null}
{
  "status": 200,
  "data": {
    "id": "665f1a2b3c4d5e6f7a8b9aa01",
    "voice_id": "665f1a2b3c4d5e6f7a8b9c0d",
    "org_id": "664a0b1c2d3e4f5a6b7c8d9e",
    "job_type": "instant_clone",
    "status": "succeeded",
    "progress": 100
  },
  "error": false
}
```

## Prompt

Copy this into your coding agent (Claude Code, Cursor, Codex…) to implement this endpoint.

```text theme={null}
You are implementing a typed client for one Finesse Voice API endpoint: "Get a job".
Everything you need is in this prompt. Do not invent anything that is not specified here.

WHAT TO BUILD
- A function getJob(jobId) (name it per the project's conventions) that returns the status of an
  asynchronous clone job started by POST /flam-voice-svc/api/v1/public/voices/clone or POST /flam-voice-svc/api/v1/public/voices/{id}/enroll.
- A function waitForJob(jobId, options) that polls getJob until the job is terminal.
- Detect the stack first: look for package.json, tsconfig.json, pyproject.toml,
  requirements.txt, go.mod, Gemfile, composer.json and similar. Use the project's language, HTTP
  client, module layout, error-handling conventions and test runner. If there is no project yet or
  the stack is unclear, use TypeScript on Node 18+ with the built-in fetch and no new dependencies.
- If the project already has a Finesse API client module, add this to it (reuse its auth, base
  URL, error type and retry helper) instead of creating a parallel one.
- Keep the base URL (https://fi.production.flamapis.com) in one constant or config value.

ENDPOINT
- Method: GET
- URL: https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/jobs/{id}

PARAMETERS
- id (path, string, required): the job_id returned by /clone or /enroll (a 24-character hex id).
  URL-encode it and reject an empty id locally.
- No query or body parameters.

AUTHENTICATION
- Send the header  X-API-Key: <workspace API key>  on every request. Keys start with "flv_".
- Read the key from the environment variable FLAM_API_KEY (or the project's existing secrets or
  config mechanism). Fail fast with a clear error if it is missing, before making any request.
- Never hard-code, log or commit the key, and never ship it in browser/client-side code: call this
  from server-side code only. The key can synthesize with every voice in its workspace.
- A key is bound to one workspace. Referencing a voice or job from another workspace returns 404.

RESPONSE ENVELOPE
JSON responses use one envelope. On success "error" is false and the payload is in "data":

    {
      "status": 200,
      "data": { "...": "endpoint payload" },
      "error": false
    }

On failure "error" is true, "data" is omitted and "message" carries the detail:

    {
      "status": 404,
      "error": true,
      "message": "Voice not found"
    }

SUCCESS RESPONSE (200, application/json)

JOB OBJECT (returned by GET https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/jobs/{id} in "data")
- id: string. The job id.
- voice_id: string. The voice this job is enrolling.
- org_id: string. The owning workspace.
- job_type: string, e.g. "instant_clone", "pro_train", "verify", "localize", "design_preview",
  "reembed".
- status: "pending" | "running" while in progress; terminal states are "succeeded", "failed",
  "cancelled" and "expired". When "succeeded", the voice's status is "ready".
- progress: integer 0-100.
- error: object, present when status is "failed"; carries the provider error detail.

    {
      "status": 200,
      "data": {
        "id": "665f1a2b3c4d5e6f7a8b9aa0",
        "voice_id": "665f1a2b3c4d5e6f7a8b9c0d",
        "org_id": "664a0b1c2d3e4f5a6b7c8d9e",
        "job_type": "instant_clone",
        "status": "succeeded",
        "progress": 100
      },
      "error": false
    }

Type status as a union of the six documented values but tolerate unknown strings (keep polling
until timeout rather than crashing).

POLLING THE JOB
- Poll GET https://fi.production.flamapis.com/flam-voice-svc/api/v1/public/jobs/{job_id} (same X-API-Key header) every ~2 seconds until
  data.status is terminal: "succeeded", "failed", "cancelled" or "expired". "pending" and "running"
  mean keep polling.
- On "succeeded" the voice is "ready" and can synthesize. On "failed" surface data.error. On
  "cancelled" or "expired" surface a clear "clone did not complete" error.
- Make the interval and an overall timeout configurable, accept an abort/cancel signal, and retry
  individual poll requests on 429, 502 and network errors with backoff (a poll is a safe GET).
- A 404 while polling means the job does not exist in this workspace: stop and surface it.

ERRORS
- 401: X-API-Key missing, malformed, invalid or revoked. Do not retry.
- 404: the job does not exist or belongs to another workspace. Do not retry.
- 429: rate limited. Retry with backoff.
- 502: upstream temporarily unavailable. Retry with backoff.
- Surface failures as one typed error (for example FinesseApiError) carrying: HTTP status, message
  (the envelope "message" or the flat "error" string), optional feature / unit / limit / used, a
  retryable flag, and the raw body for debugging.
- Parse error bodies defensively: if a body is not valid JSON, keep the raw text in the error.

RETRIES
- Retry only 429 (rate limited), 502 (provider unavailable or warming up) and network errors or
  timeouts, with exponential backoff plus jitter (for example about 1s, 2s, 4s; at most 3 retries),
  then surface the error.
- Never retry 400, 401, 402, 403, 404 or 422: they fail the same way again.

ACCEPTANCE CRITERIA
- getJob returns the typed job from "data".
- waitForJob resolves with the job on "succeeded"; rejects with a descriptive error on "failed"
  (including data.error), "cancelled", "expired", or when its configurable timeout elapses; stops
  promptly when aborted; and reports progress (0-100) to an optional callback.
- 404 and 401 are not retried; transient poll failures are.

TEST PLAN
- Unit tests with mocked HTTP and fake timers: pending -> running(40) -> succeeded(100); running
  -> failed with an error object; expired; timeout; abort; 404; 502 then 200.
- Smoke script: given FLAM_JOB_ID, print status and progress once, or poll with --wait until
  terminal. Exit non-zero on error.

RULES
- Implement only what this spec describes. Do not invent parameters, fields, headers, endpoints or
  error codes. If something is ambiguous, missing, or conflicts with the existing codebase, stop
  and ask me before guessing.
- Match the project's existing style and conventions; do not refactor unrelated code.
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.