CLI and SDK
Two thin layers over the same API, for people and agents who would rather not write the requests by hand. Both are in the platform's repository today and will be published as packages at launch.
ff, the command line
No dependencies. Every command is a call to /v1; --json prints the answer as it came, --all follows every page, and ff call reaches any route the OpenAPI document lists.
ff login --key <api key> --base https://app.final-frame.com/api ff whoami ff projects --direction outbound ff works --territory GB --from 2027-01-01 --to 2027-12-31 ff check --identity "eidr:10.5240/..." --right svod --territory GB --from 2027-01-01 ff work-order create <projectId> --name "Subtitle the trailer" --kind subtitling ff config export > final-frame.json ff config apply final-frame.json ff call GET /v1/projects --json
The key lives in ~/.config/final-frame/config.json (mode 600) or in FF_API_KEY; FF_API_BASE picks the environment. ff --help lists everything.
Configuration as code
An organisation's set-up is one document: delivery specs, house formats, the metadata schema and every taxonomy. GET /v1/config exports it; PUT /v1/config applies it, upserting specs by name and taxonomy terms by key, replacing the formats and the schema when given, and never deleting. Keep it in your own repository, copy a set-up between organisations with two commands, or let an agent set a new one up.
TypeScript SDK
import { createClient } from '@finalframe/sdk';
const ff = createClient({ apiKey: process.env.FF_API_KEY!, baseUrl: 'https://app.final-frame.com/api' });
for await (const project of ff.projects.all()) console.log(project.reference, project.name);
const answer = await ff.rights.availability({ identity, right: 'svod', territory: 'GB', from: '2027-01-01' });
const order = await ff.workOrders.create(projectId, { name: 'Subtitle the trailer', kind: 'subtitling' });Every list has list() for one page and all() for every page. Writes are idempotent: pass idempotencyKey, or the client derives one from the body. Errors are FinalFrameError with the status, the code, the message and the scope a 403 named. A Python SDK, generated from the OpenAPI document, follows at launch.