# Agent quickstart

> The brief for an LLM agent writing code against the FrameQuery API. Auth, the three calls, exact JSON shapes and which errors to retry.

Source: https://www.framequery.com/docs/agent-quickstart (updated 2026-10-13)

## The brief

Base `https://api.framequery.com/v1/api`. Header `Authorization: Bearer $FRAMEQUERY_API_KEY` (keys start `fq_live_`, server-side only). Success `{"data": …}`; error `{"error", "code", "docUrl", "param"}`: branch on `code`.

1. **Create.** `POST /jobs/from-url`, header `Idempotency-Key: <uuid>`, body `{"url": "https://…/a.mp4", "processingMode": "all"}` (`all` | `transcript` | `vision`) → `data.jobId`.

   Local file: `POST /jobs` `{"fileName": "a.mp4", "fileSizeBytes": 1048576}`, then, within an hour, `PUT` the bytes to `data.uploadUrl` sending every `data.uploadHeaders` entry and no `Authorization`. The URL accepts exactly one object (a `412` means it already landed; read the job rather than re-uploading).
2. **Wait.** `GET /jobs/{jobId}?include=none` every 5–15 s until `data.state` is `completed`, `failed`, `cancelled` or `expired` (ignore `status`). Or pass `"callbackUrl"` at create for a signed webhook.
3. **Read.** `GET /jobs/{jobId}/transcript` → `data.text`, `data.speakers[]`, `data.segments[{startTime, endTime, text, speaker}]`; `?format=srt|vtt|txt` for a file. `GET /jobs/{jobId}/scenes` → `data.scenes[{index, startTime, endTime, description, objects[], colorPalette[], shotType, shotAngle}]`. Times in seconds.

**Errors.** Retry 429, 503 and 409 `IDEMPOTENCY_IN_PROGRESS` after `Retry-After`; retry 500 with exponential backoff. Reuse the same `Idempotency-Key` on every retry. 409 `RESULTS_NOT_READY`: later. 404 `RESULTS_UNAVAILABLE`: never (see `reason`). 402: out of credits. Others: fix the request. `state` `failed` → `data.error` `{code, message, stage}`; completed stages stay readable.

## Copy-paste script

The brief above as a runnable bash script (needs `curl` and `jq`):

```bash
set -euo pipefail
API=https://api.framequery.com/v1/api
AUTH="Authorization: Bearer $FRAMEQUERY_API_KEY"

JOB_ID=$(curl -sS -X POST "$API/jobs/from-url" \
  -H "$AUTH" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"url": "https://example.com/interview.mp4", "processingMode": "all"}' | jq -r .data.jobId)

while :; do
  STATE=$(curl -sS "$API/jobs/$JOB_ID?include=none" -H "$AUTH" | jq -r .data.state)
  echo "state: $STATE"
  case "$STATE" in completed|failed|cancelled|expired) break ;; esac
  sleep 10
done

curl -sS "$API/jobs/$JOB_ID/transcript" -H "$AUTH" | jq '.data.segments[:3]'
curl -sS "$API/jobs/$JOB_ID/scenes" -H "$AUTH" | jq '.data.scenes[:3]'
```

## With an SDK

The Python and TypeScript SDKs do the create, upload, wait and fetch in one call, with idempotency keys and retries built in:

```python
from framequery import FrameQuery

fq = FrameQuery()  # reads FRAMEQUERY_API_KEY
result = fq.process("https://example.com/interview.mp4")  # or a local path
print(result.transcript.text if result.transcript else "no transcript")
for scene in result.scenes or []:
    print(scene.start_time, scene.description)
```

```typescript
import FrameQuery from "framequery";

const fq = new FrameQuery(); // reads FRAMEQUERY_API_KEY
const result = await fq.process("https://example.com/interview.mp4"); // or a local path
console.log(result.transcript?.text ?? "no transcript");
for (const scene of result.scenes ?? []) console.log(scene.startTime, scene.description);
```

## Rules of thumb

- Decide everything from `state` and `stages.*.status`. The `status` string is a legacy internal value and changes without notice.
- Poll no faster than every 5 seconds; `estimatedCompletionTimeSeconds` on the job is a good hint. GET requests are limited to 600 per minute per account.
- Send an `Idempotency-Key` on every create and reuse it on retries. A replay returns the original job with `Idempotent-Replayed: true`.
- A failed or skipped stage costs nothing. A job whose vision stage failed can still have a completed, readable (and charged) transcript.
- Never ship the key to a browser or mobile app. Proxy through your server.
- Cost per footage-hour: $1.50 `all`, $1.15 `vision`, $0.35 `transcript` (per audio track); camera RAW $2.50 / $1.65 / $0.85. Per second, 30 s minimum per job. Check before spending with `GET /pricing/estimate?durationSeconds=600&mode=all` (no key) and `GET /quota` (`data.api.availableCents`).
- More detail: [Jobs](https://www.framequery.com/docs/jobs), [Job lifecycle](https://www.framequery.com/docs/job-lifecycle), [Results](https://www.framequery.com/docs/results), [Errors](https://www.framequery.com/docs/errors), [OpenAPI](https://api.framequery.com/v1/api/openapi.json).
