Video Edit by Chat
Describe a change to a video clip in plain language (Polish or English), select the part it applies to, and get the edited clip back. "Make the sky a sunset", "remove the person on the left", "slow this down", "continue the scene for five more seconds". FOTOhub AI reads the instruction, picks the route that fits it, edits only the selected range and splices it back into your original, so everything outside the range is untouched, frame for frame.
A session is a conversation about one clip. Every turn is one instruction and produces a new version of the clip (the head). You can undo, revert to any earlier version or to the original, and keep editing the result.
Capabilities
- Edit a range or extend the clip: edits take a
selection(a few seconds); extensions add seconds before or after - Follow-ups: "make it a bit brighter" is understood in the context of the previous turns
- Versioning:
undo,revert, history of turns, results stored in your library - Quote before you run: the quote names the route, the billable seconds and the price
- Refunded on failure: a turn that fails or is cancelled is refunded exactly once
- Disclosure included: every result states that the edited range is AI generated
Availability
Video Edit by Chat is rolled out in stages. Until it is enabled for your account, every route answers 403 with {"error": "feature_disabled", "feature": "video_chat_edit"}. Nothing is charged. Paying with credits is limited to the plan returned by config().credits_min_plan; everyone else pays from the prepaid USD wallet (see payment in the quote).
Endpoints
| Method | Path | Charged | Description |
|---|---|---|---|
| GET | /v1/aiwave/video-edit/config | no | Limits, public route names, price multipliers, disclosure and likeness notice, whether you may pay with credits |
| POST | /v1/aiwave/video-edit/sessions | no | Start a session on one of your clips |
| GET | /v1/aiwave/video-edit/sessions | no | Your recent sessions |
| GET | /v1/aiwave/video-edit/sessions/{id} | no | The session, its head clip, every turn with status and ETA |
| DELETE | /v1/aiwave/video-edit/sessions/{id} | no | Archive (the outputs stay in your storage) |
| POST | /v1/aiwave/video-edit/sessions/{id}/quote | no | Intent, route, billable seconds, price, availability, ETA |
| POST | /v1/aiwave/video-edit/sessions/{id}/turns | yes | One edit turn |
| POST | /v1/aiwave/video-edit/sessions/{id}/undo | no | Head back to the version before the last edit |
| POST | /v1/aiwave/video-edit/sessions/{id}/revert | no | Head to any delivered turn, or to the original |
| GET | /v1/aiwave/video-edit/turns/{turn_id} | no | Status, stage, result or error, refund state |
| POST | /v1/aiwave/video-edit/turns/{turn_id}/cancel | no | Cancel; refunded once |
Base URL: https://apis.fotohub.appAuthentication: Authorization: Bearer fh_live_your_api_key (see Authentication)
Pricing
Nothing is hard-coded in this page: the price comes from the API, and you see it before you pay.
POST /sessions/{id}/quotereturnsprice_key,units,credits,usd,available, theplan(route, billable seconds, price multiplier) andpayment(how you pay:credits_or_walletorwallet).- The unit is a billable second of the edited range, multiplied by the price multiplier of the route that will run (
config().price_multipliers, keyed by the public route names below). The USD wallet rate of the price keyaiw-videoedit-sis listed byGET /v1/pricing:GET /v1/pricing?model=aiw-videoedit-s. While the feature is disabled,GET /v1/pricing?model=aiw-*answers404for its keys. - The quote always names the route that will actually run. If the preferred route cannot be offered at a sustainable price, the fallback route is quoted instead, and
plan.fallbacksays so.
Routes
Responses use public route names only:
| Route | Used for |
|---|---|
fotohub-video-edit-conversational | Restyle, replace and adjust ("make it look like film", "turn the car red") |
fotohub-video-edit-precise | Add and remove ("remove the sign", "add a dog") and the fallback for the route above; keeps the clip's aspect ratio and duration |
fotohub-video-extend | Extend the clip forward or backward |
Edit a clip
from fotohub import FotoHub
client = FotoHub() # reads FOTOHUB_API_KEY
session = client.video_edit.create_session(
"videos/<your-user-id>/beach.mp4", duration_s=14.2, width=1920, height=1080, fps=30
)["session"]
quote = client.video_edit.quote(
session["id"], "Make the sky a warm sunset", selection=(2.0, 6.0)
)
print(quote["plan"]["route"], quote["usd"], quote["payment"], quote["available"])
turn = client.video_edit.edit(
session["id"], "Make the sky a warm sunset", selection=(2.0, 6.0)
)["turn"]
done = client.video_edit.wait_for_turn(turn["turn_id"], on_progress=lambda v: print(v["stage"]))
print(done["result"]["url"], done["disclosure"]["text"])
client.video_edit.edit(session["id"], "A bit less orange", selection=(2.0, 6.0)) # follow-up
client.video_edit.undo(session["id"]) # back one versionimport { FotoHub } from "fotohub";
const client = new FotoHub({ apiKey: process.env.FOTOHUB_API_KEY! });
const { session } = (await client.videoEdit.createSession({
sourcePath: "videos/<your-user-id>/beach.mp4",
durationS: 14.2, width: 1920, height: 1080, fps: 30,
})) as any;
const { turn } = (await client.videoEdit.edit(session.id, {
instruction: "Make the sky a warm sunset",
selection: [2.0, 6.0],
})) as any;
const done = await client.videoEdit.waitForTurn(turn.turn_id, {
onProgress: (v) => console.log(v.stage),
});
console.log((done.result as any).url);curl https://apis.fotohub.app/v1/aiwave/video-edit/sessions/$SESSION/turns \
-H "Authorization: Bearer $FOTOHUB_API_KEY" -H "Content-Type: application/json" \
-d '{
"instruction": "Make the sky a warm sunset",
"selection": {"start_s": 2.0, "end_s": 6.0},
"request_id": "6f1d3c52-8a0e-4b49-9c1f-0a2b3c4d5e6f"
}'Start a session
POST /sessions takes source_path (a clip in your own storage: videos/<your user id>/... or photos/<your user id>/...) and clip (duration_s, width, height, optional fps), plus an optional title. Clips longer than config().limits.max_clip_s are refused with 422 clip_too_long. Starting a session is free.
A turn
| Field | Type | Description |
|---|---|---|
instruction | string | Required. What to change, up to limits.max_instruction_chars characters |
selection | {start_s, end_s} | The range to edit, in seconds of the current head clip. Required for edits; between limits.min_selection_s and limits.max_selection_s long. Not used for extensions |
extend_seconds | integer | Seconds to add for an extension (limits.extend_s.min to limits.extend_s.max) |
audio | "original" | "edited" | Which audio the result keeps (edits keep the original by default) |
likeness_ack | bool | Set to true when the quote says likeness_ack_required |
request_id | UUID | Idempotency key: the same request_id returns the turn it already created (replayed: true) and never bills twice. The SDKs generate one |
The instruction is classified into an intent (remove, add, replace, restyle, adjust, extend_forward, extend_backward). Short follow-ups such as "a bit less" inherit the intent of the previous turn. The quote returns the intent it chose.
Answer: {"turn": {"turn_id", "status", "seq", ...}, "replayed", "eta", "quote", "payment", "likeness"}.
Result
A succeeded turn's result holds url (the new version of the clip), poster_url, the route, intent, selection, duration_s, width, height, fps, audio, verified_exact (the part outside the selection was checked against your original), the clip description for the next turn and the disclosure. The new version is stored in your library.
Versions
POST /sessions/{id}/undo: head to the version before the last applied edit.POST /sessions/{id}/revertwith{"turn_id": "<uuid>"}: head to a delivered turn; with{"turn_id": null}: back to the original clip.- The next turn always edits the current head. One turn at a time per session (
409 turn_in_progressotherwise).
Turn states and refunds
status: queued, running, succeeded, failed, cancelled, with a stage and progress. A failed turn has error: {"code", "message"} such as provider_refused (the safety filter refused the instruction), provider_failed, provider_timeout, splice_failed, and is refunded exactly once (refunded, refund_message). POST /turns/{id}/cancel refunds a queued or running turn.
Safety, likeness and labelling
- Instructions are screened before anything is charged. Edits that sexualise people, undress them or involve minors in that way are refused with
422 instruction_blocked. - When an instruction changes how people look or who they appear to be, the quote sets
likeness_ack_required: true. Sendlikeness_ack: trueto confirm you have the right to edit the footage and the consent of every identifiable person in it (config().likeness_notice). Without it the turn is refused with422 likeness_ack_required, uncharged. - The edited range is AI generated and you should label the result as such where you publish it. Depending on the route, the range carries an invisible watermark that identifies it as AI generated: do not remove it. The exact wording is returned in
disclosureon every quote and result.
Where processing happens
The edit itself is carried out by FOTOhub AI provider partners outside the EU; your clip's selected range is sent to them for the duration of the turn. Trimming, splicing back into the original and storage run on FOTOhub's EU infrastructure. The statement is returned as config().residency and in every quote. Mention this in your own privacy policy if you offer the feature to end users.
Errors
| Status | error | Meaning |
|---|---|---|
| 401 | authentication_required | No or invalid key |
| 402 | insufficient_funds | Wallet (or credits) too low; required_usd, balance_usd, shortfall_usd, topup_url |
| 403 | feature_disabled | Not enabled for your account yet |
| 404 | session_not_found, turn_not_found | Not yours or gone |
| 409 | turn_in_progress, session_archived, not_cancellable, session_clip_unknown | State conflict |
| 422 | invalid_source_path, clip_too_long, selection_required, invalid_selection, instruction_blocked, likeness_ack_required, aspect_not_supported, clip_too_short | Fixable input; nothing charged |
| 429 | ceiling_exceeded | Start rate, active turns or the daily ceiling; Retry-After; nothing charged |
| 503 | backend_unavailable, store_unavailable | Temporarily not offered; Retry-After; not charged |
Related
- Video Editing and Video Generation
- Music Edits: a soundtrack for the edited clip
- Billing & Pricing and
GET /v1/pricing - Python SDK and TypeScript SDK

