API Reference

FrameQuery API

Programmatically upload videos and trigger processing through the FrameQuery cloud pipeline. Integrate FrameQuery into your workflow, automate bulk uploads, or build custom tooling on top of your video library.

Base URL

https://api.framequery.com/v1

All requests must be made over HTTPS.

Auth

API key via header

Rate limit

120 req/min per key

Pricing

From $1.50 / hour indexed

Quick start

Process a video via URL in two API calls:

1Create an API key from your dashboard
2Submit a job with a video URL (or request a presigned upload URL)
3Poll the job status until processing completes
# Submit a video from URL
curl -X POST https://api.framequery.com/v1/api/jobs/from-url \
  -H "X-API-Key: fq_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/video.mp4"}'

Response envelope

All successful responses are wrapped in a data key. Errors return an error string.

{
  "data": { ... }
}

Authentication

All API requests require authentication via an API key. Include your key in the request headers using either format:

X-API-Key: fq_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6

API keys use the fq_live_ prefix followed by 32 hex characters. Create and manage keys from your dashboard. See the section below.

Important: Keep your API keys secret. Do not expose them in client-side code, public repositories, or share them in plain text. If a key is compromised, revoke it immediately and generate a new one.

API Keys

Create and manage API keys directly from here. Each key is identified by its keyPrefix, the first 8 hex characters after fq_live_. Keys support scopes: jobs:read, jobs:write, and quota:read.

Loading...

API reference

You can also manage keys programmatically. These endpoints are authenticated via your session token (not an API key).

POST/v1/api/keys

Generate a new API key. The full key is only returned once. Store it securely.

Body parameters

ParameterTypeDescription
namestringA human-readable label for this key (e.g. "CI Pipeline")
scopesstring[]Permissions for this key. Defaults to ["jobs:read", "jobs:write", "quota:read"]
curl -X POST https://api.framequery.com/v1/api/keys \
  -H "Authorization: Bearer <your_session_token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "CI Pipeline"}'

Response

200
JSON
{
  "data": {
    "key": "fq_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
    "keyPrefix": "a1b2c3d4",
    "name": "CI Pipeline"
  }
}
Important: The full API key is only returned in this response. Store it in a secure location like a secrets manager or environment variable. It cannot be retrieved again.

GET/v1/api/keys

List all API keys associated with your account. Returns the key prefix, name, scopes, and revocation status, never the full key.

curl https://api.framequery.com/v1/api/keys \
  -H "Authorization: Bearer <your_session_token>"

Response

200
JSON
{
  "data": [
    {
      "keyPrefix": "a1b2c3d4",
      "name": "CI Pipeline",
      "scopes": ["jobs:read", "jobs:write", "quota:read"],
      "createdAt": "2026-02-21T14:30:00Z",
      "revoked": false
    },
    {
      "keyPrefix": "e5f6a7b8",
      "name": "Staging",
      "scopes": ["jobs:read", "quota:read"],
      "createdAt": "2026-02-19T09:00:00Z",
      "revoked": false
    }
  ]
}

DELETE/v1/api/keys/:keyPrefix

Permanently revoke an API key. Any requests using this key will immediately start failing with 401.

Path parameters

ParameterTypeDescription
keyPrefixstringThe 8-character key prefix (e.g. a1b2c3d4)
curl -X DELETE https://api.framequery.com/v1/api/keys/a1b2c3d4 \
  -H "Authorization: Bearer <your_session_token>"

Response

200
JSON
{
  "data": {
    "revoked": true
  }
}
Note: Revocation is immediate and irreversible. Create a new key if you need to restore access.

Jobs

A job represents a video being uploaded and processed through the FrameQuery pipeline. Once complete, the job contains scenes, transcripts, and detected objects. Jobs progress through these statuses:

PENDING_UPLOAD
PENDING_FETCH
INGEST_PROCESSING
VISION_COMPLETED
StatusMeaning
PENDING_UPLOADJob created via presigned URL. Waiting for you to PUT the file
PENDING_FETCHJob created from URL. FrameQuery is downloading the file
INGEST_PROCESSINGVideo is being transcoded and ingested
INGEST_TRANSCODINGFFmpeg transcode in progress
INGEST_COMPLETEDIngest finished, moving to scene detection
VISION_PROCESSINGAI scene detection and object recognition in progress
VISION_COMPLETEDProcessing finished with scenes and transcript
VIDEO_COMPLETED_NO_SCENESProcessing finished but no scenes were detected
FAILED_FETCHURL download failed (unreachable, private IP, etc.)
INGEST_FAILED_TRANSCODEVideo format unsupported or transcode failed
Tip: Camera RAW formats (R3D, BRAW) are supported and automatically transcoded to H.264 proxy before processing. See supported formats.
POST/v1/api/jobs

Create a job and get a presigned upload URL. You then PUT your file directly to that URL.

Body parameters

ParameterTypeDescription
fileNamestringThe filename including extension (e.g. "video.mp4")
callbackUrlstringHTTPS webhook URL — receives a POST when the job completes or fails
processingModestring"all" (default), "transcript" (audio only, 50% off), or "vision" (video only, 50% off)
idempotencyKeystringClient-generated string to prevent duplicate job creation (24h TTL)
audioTracksAudioTrack[]Array of audio tracks for multi-track transcription (max 16). Each track gets its own presigned upload URL. Mutually exclusive with audioFileName.

AudioTrack object

ParameterTypeDescription
fileNamestringAudio filename including extension (e.g. "boom.wav")
syncModestring"auto" (default), "timecode" (SMPTE/BWF), or "offset" (manual ms offset)
offsetMsnumberManual offset in milliseconds when syncMode="offset" (range: ±24h)
labelstringHuman-readable label (e.g. "Host", "Guest"). Defaults to track_N.
perChannelTranscriptionbooleanIf true, each channel/track gets its own independent transcript with separate speaker diarization. If false (default), all audio is mixed to mono for a single unified transcript. Each channel is billed separately when enabled.
channelsnumber[]Select specific channels from a polyphonic file (0-indexed). Omit to transcribe all channels.
# Step 1: Create the job and get an upload URL
curl -X POST https://api.framequery.com/v1/api/jobs \
  -H "X-API-Key: fq_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"fileName": "video.mp4"}'

# Step 2: Upload the file to the presigned URL (expires in 15 min)
curl -X PUT "<uploadUrl from step 1>" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @/path/to/video.mp4

Response

200
JSON
{
  "data": {
    "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "uploadUrl": "https://storage.googleapis.com/...",
    "expiresInSeconds": 900,
    "uploadMethod": "PUT"
  }
}
Note: The presigned URL expires after 15 minutes. Upload the file using a PUT request with Content-Type: application/octet-stream. Processing starts automatically once the upload completes.

POST/v1/api/jobs/from-url

Submit a publicly accessible video URL for FrameQuery to download and process. No file upload needed.

Body parameters

ParameterTypeDescription
urlstringPublicly accessible URL of the video file (http or https)
fileNamestringOverride the filename (defaults to filename from URL)
downloadTokenstringBearer token for authenticated downloads (e.g. Google Drive OAuth token)
providerstringCloud provider hint: "gdrive", "dropbox", or omit for plain URLs
callbackUrlstringHTTPS webhook URL — receives a POST when the job completes or fails
processingModestring"all" (default), "transcript" (audio only, 50% off), or "vision" (video only, 50% off)
idempotencyKeystringClient-generated string to prevent duplicate job creation (24h TTL)
audioTracksAudioTrack[]Array of audio tracks with URLs for multi-track transcription (max 16). Each track includes url, syncMode, offsetMs, label, and perChannelTranscription. Mutually exclusive with audioUrl.
curl -X POST https://api.framequery.com/v1/api/jobs/from-url \
  -H "X-API-Key: fq_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://storage.example.com/rushes/A003_C008.R3D",
    "fileName": "Camera A Take 8.R3D"
  }'

Response

200
JSON
{
  "data": {
    "jobId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
    "status": "PENDING_FETCH"
  }
}
Note: The URL must be publicly accessible. The server rejects URLs pointing to private or internal IP ranges (SSRF protection).

GET/v1/api/jobs/:jobId

Retrieve the current status and details of a job. Completed jobs include the full processedData with scenes and transcript.

Path parameters

ParameterTypeDescription
jobIdstringThe job ID
curl https://api.framequery.com/v1/api/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "X-API-Key: fq_live_your_key_here"

Response (in progress)

Response

200
JSON
{
  "data": {
    "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "PENDING_FETCH",
    "originalFilename": "A003_C008.R3D",
    "source": "api",
    "createdAt": "2026-02-21T14:30:00Z",
    "updatedAt": "2026-02-21T14:30:00Z"
  }
}

Response (completed)

Response

200
JSON
{
  "data": {
    "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "VISION_COMPLETED",
    "originalFilename": "A003_C008.R3D",
    "source": "api",
    "createdAt": "2026-02-21T14:30:00Z",
    "updatedAt": "2026-02-21T14:38:42Z",
    "processedData": {
      "length": 1847.5,
      "scenes": [
        {
          "sceneId": "scene_a3f7c291-8e4b-4d1a-b562-9c0e3f8a71d4_45.8",
          "startTs": 12.467,
          "endTs": 45.8,
          "description": "A woman in a blue blazer stands at the front of the room presenting a bar chart showing revenue growth across four quarters.",
          "mainColor": "#3b82f6",
          "detections": [
            {
              "name": "person",
              "x": 0.12,
              "y": 0.08,
              "width": 0.22,
              "height": 0.88,
              "confidence": 0.9789
            },
            {
              "name": "bar chart",
              "x": 0.42,
              "y": 0.05,
              "width": 0.52,
              "height": 0.68,
              "confidence": 0.9156
            }
          ]
        }
      ],
      "transcript": [
        {
          "Speaker": "a4b2c3d4-e5f6-7890-abcd-ef1234567890",
          "StartTime": 12.4,
          "EndTime": 18.9,
          "Text": "Welcome everyone to today's product reveal."
        },
        {
          "Speaker": "a4b2c3d4-e5f6-7890-abcd-ef1234567890",
          "StartTime": 19.2,
          "EndTime": 27.1,
          "Text": "What we're about to show you has been two years in the making."
        }
      ]
    }
  }
}
Note: The processedData field is only present when the job status is VISION_COMPLETED. Transcript fields use capitalized keys: StartTime, EndTime, Text. Times are in seconds.

Multi-Track Audio

FrameQuery supports two audio modes when you submit external audio:

Mixed (default)

All audio tracks are mixed down to mono and processed as a single transcript with unified speaker diarization. This is the cheapest option and works well when you just need the words from all sources combined. Billed as 1 transcript channel regardless of how many files you submit.

Per-channel transcription

Each audio track (or each channel in a polyphonic BWF) gets its own independent transcript with separate speaker diarization. Use this when you need to know exactly what each microphone captured — e.g. isolating the host vs. guest in an interview, or getting per-cast-member dialogue from a reality TV field recorder. Set perChannelTranscription: true on the audio track and each channel is billed separately (0.5x base per channel per hour).

iXML track names from professional field recorders (Sound Devices, Zoom F-series) are automatically detected and returned in the response.

GET/v1/api/jobs/:jobId/audioTracks

Retrieve all audio track transcripts for a multi-track job.

curl https://api.framequery.com/v1/api/jobs/a1b2c3d4-.../audioTracks \
  -H "X-API-Key: fq_live_your_key_here"

Response

200
JSON
{
  "data": {
    "jobId": "a1b2c3d4-...",
    "tracks": [
      {
        "trackIndex": 0,
        "trackName": "Boom",
        "language": "en",
        "status": "STT_COMPLETED",
        "transcript": [{"startTime": 0.0, "endTime": 2.5, "text": "..."}],
        "speakers": ["Speaker 1"]
      },
      {
        "trackIndex": 1,
        "trackName": "Lav-Host",
        "language": "en",
        "status": "STT_COMPLETED",
        "transcript": [{"startTime": 0.1, "endTime": 2.4, "text": "..."}],
        "speakers": ["Speaker 1"]
      }
    ]
  }
}
GET/v1/api/jobs/:jobId/audioTracks/:trackIndex

Retrieve a single audio track transcript by index.

Note: Multi-track jobs include audioTrackCount, audioTracksCompleted, and audioTrackNames in the job response. Track transcripts are available progressively as each track completes STT processing.

Example: separate audio files (interview with 2 lavs)

curl -X POST https://api.framequery.com/v1/api/jobs/from-url \
  -H "X-API-Key: fq_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://cdn.example.com/interview.mov",
    "audioTracks": [
      {"url": "https://cdn.example.com/host-lav.wav", "syncMode": "timecode", "label": "Host"},
      {"url": "https://cdn.example.com/guest-lav.wav", "syncMode": "timecode", "label": "Guest"}
    ]
  }'

Example: polyphonic BWF with per-channel splitting

# Upload a multi-channel BWF with per-channel splitting
curl -X POST https://api.framequery.com/v1/api/jobs \
  -H "X-API-Key: fq_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "fileName": "interview.mov",
    "audioTracks": [
      {
        "fileName": "field-recorder.wav",
        "syncMode": "timecode",
        "perChannelTranscription": true
      }
    ]
  }'

# Response includes audioTrackUploadUrls for each audio file

GET/v1/api/jobs

List jobs for your account with optional filtering. Uses cursor-based pagination.

Query parameters

ParameterTypeDescription
statusstringFilter by exact status (e.g. VISION_COMPLETED, PENDING_FETCH)
limitintegerResults per page, 1–100 (default: 20)
cursorstringJob ID from a previous response's nextCursor for pagination
curl "https://api.framequery.com/v1/api/jobs?status=VISION_COMPLETED&limit=10" \
  -H "X-API-Key: fq_live_your_key_here"

Response

200
JSON
{
  "data": [
    {
      "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "VISION_COMPLETED",
      "originalFilename": "video.mp4",
      "createdAt": "2026-02-21T14:30:00Z",
      "updatedAt": "2026-02-21T14:38:42Z"
    }
  ],
  "nextCursor": "c3d4e5f6-a7b8-9012-cdef-345678901234"
}
Tip: To paginate through all results, keep passing the nextCursor value as the cursor query parameter until nextCursor is null.

Batch processing

Submit multiple video URLs in a single request. Choose independent mode (each clip is a separate job) or continuous mode (clips are concatenated and processed as one video).

POST/v1/api/jobs/batch

Submit a batch of video URLs for processing.

Body parameters

ParameterTypeDescription
clipsarrayArray of clip objects (max 100)
modestring"independent" (separate jobs) or "continuous" (concatenated into one)
processingModestring"all" (default), "transcript", or "vision"
callbackUrlstringHTTPS webhook URL for job completion notifications
Note: Each clip object supports these fields: sourceUrl (required), fileName (optional), downloadToken (optional), provider (optional).
curl -X POST https://api.framequery.com/v1/api/jobs/batch \
  -H "X-API-Key: fq_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "mode": "independent",
  "clips": [
    { "sourceUrl": "https://cdn.example.com/clip1.mp4" },
    { "sourceUrl": "https://cdn.example.com/clip2.mp4" }
  ],
  "processingMode": "all"
}'

Response

200
JSON
{
  "data": {
    "batchId": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
    "mode": "independent",
    "jobs": [
      { "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "PENDING_FETCH" },
      { "jobId": "b2c3d4e5-f6a7-8901-bcde-f23456789012", "status": "PENDING_FETCH" }
    ]
  }
}
Note: In independent mode, each clip becomes a separate job that processes in parallel. In continuous mode, all clips are concatenated into a single video and processed as one job — useful for multi-camera shoots of the same event.

Billing & Credits

Video processing is billed per second based on the duration of the source video, with a 30-second minimum per job. Pricing depends on the codec: camera RAW formats cost more due to heavier decode and analysis. Volume discounts kick in automatically after 500 hours per month.

Pricing

TierFormatsRate500+ hr rate
RAWARRIRAW (.ari/.arx), R3D, BRAW$2.50/hr ($0.042/min)$2.00/hr ($0.033/min)
NormalAll other formats$1.50/hr ($0.025/min)$1.00/hr ($0.017/min)

Billed per second with a 30-second minimum per job.

Transcript-only or vision-only

Need just the transcript or just the visual analysis? Each is available at 50% of the full rate. Pass processingMode: "transcript" or processingMode: "vision" when submitting a job.

Multi-track audio pricing

Vision is billed once at 50% of the base rate. Transcript is billed at 50% per channel. For example, a 1-hour Normal-tier video with 4 audio channels in "all" mode: 0.5 × $1.50 (vision) + 4 × 0.5 × $1.50 (transcript) = $3.75. Without multi-track, the same job costs $1.50 (1 channel default).

Cost examples (Normal tier)

Video durationBilled durationCost
10 seconds30 seconds (minimum)$0.013
5 minutes5 min 0 sec$0.125
45 minutes45 min 0 sec$1.13
1 hour1 hr 0 min 0 sec$1.50
3 hr 10 min3 hr 10 min 0 sec$4.75

Volume discount

After 500 hours of processing in a billing month, all additional usage is billed at the reduced rate: $2.00/hr for RAW, $1.00/hr for Normal. The discount applies automatically.

How FrameQuery compares

FrameQuery delivers scene detection, transcription, object recognition, and semantic search in a single API call, no stitching services together.

 FrameQueryTwelve LabsGoogle Video AIAWS Rekognition
Price / hr$1.50 / $2.50 RAW$2.40~$4.68*~$4.32*
Volume rate$1.00 / $2.00 RAW after 500 hrCustomNoneNone
Billing granularityPer secondPer secondPer 1-min chunkPer 1-min chunk
Minimum charge30 sec10 sec1 min1 min
Scene detectionIncludedIncludedAdd-onNot available
TranscriptionIncludedIncludedSeparate APISeparate API
Object detectionIncludedLimitedAdd-onIncluded
Semantic searchIncludedIncludedNot availableNot available
Speaker diarizationIncludedNot availableSeparate APINot available
Camera RAW supportIncludedNot availableNot availableNot available
Single API callYesYesNo (multiple)No (multiple)

* Estimated cost when combining label detection, transcription, and object tracking at published per-minute rates. Actual costs vary by feature selection.

Check your quota

GET/v1/api/quota

Retrieve your current plan, included hours, credit balance, and reset date.

curl https://api.framequery.com/v1/api/quota \
  -H "X-API-Key: fq_live_your_key_here"

Response

200
JSON
{
  "data": {
    "currentPlan": "pro",
    "totalHours": 55,
    "usedHours": 3,
    "remainingHours": 52,
    "includedHours": 50,
    "creditsBalanceHours": 5,
    "resetDate": "2026-03-01T00:00:00Z"
  }
}

Errors

When a request fails, the API returns a JSON body with an error string and an appropriate HTTP status code.

{
  "error": "The provided API key is invalid or has been revoked."
}

Common error responses

StatusDescription
400Bad request: missing required fields or malformed body
401Unauthorized: missing or invalid API key
402Payment Required: insufficient credits or quota exceeded
403Forbidden: missing scopes or permissions
404Not found: the requested job or resource does not exist
429Rate limited: too many requests, retry after the reset window
500Internal error: something went wrong on our end

Rate Limits

Rate limits protect the API from abuse and ensure fair usage across all accounts. Limits are applied per API key.

EndpointLimit
General120 requests / minute (2/sec, burst 20)
Uploads20 concurrent active jobs
Key management10 requests / minute

Rate limit information is included in response headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1708523400

When rate limited, the API returns 429 Too Many Requests. Use the X-RateLimit-Reset header (Unix timestamp) to determine when you can retry.

Tip: Implement exponential backoff in your integration to handle rate limits gracefully. Most HTTP client libraries support automatic retries with backoff.

Limits

The following limits apply to all accounts. Contact support if you need higher limits for your use case.

LimitValue
Max file size20 GB per upload
Max video duration12 hours per job (contact support@framequery.com for extensions)
Max batch size100 clips per batch request
Upload URL expiry15 minutes after creation

Need help?

Having trouble with the API? Reach out and we'll get you sorted.