Skip to content

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 ​

MethodPathChargedDescription
GET/v1/aiwave/video-edit/confignoLimits, public route names, price multipliers, disclosure and likeness notice, whether you may pay with credits
POST/v1/aiwave/video-edit/sessionsnoStart a session on one of your clips
GET/v1/aiwave/video-edit/sessionsnoYour recent sessions
GET/v1/aiwave/video-edit/sessions/{id}noThe session, its head clip, every turn with status and ETA
DELETE/v1/aiwave/video-edit/sessions/{id}noArchive (the outputs stay in your storage)
POST/v1/aiwave/video-edit/sessions/{id}/quotenoIntent, route, billable seconds, price, availability, ETA
POST/v1/aiwave/video-edit/sessions/{id}/turnsyesOne edit turn
POST/v1/aiwave/video-edit/sessions/{id}/undonoHead back to the version before the last edit
POST/v1/aiwave/video-edit/sessions/{id}/revertnoHead to any delivered turn, or to the original
GET/v1/aiwave/video-edit/turns/{turn_id}noStatus, stage, result or error, refund state
POST/v1/aiwave/video-edit/turns/{turn_id}/cancelnoCancel; 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}/quote returns price_key, units, credits, usd, available, the plan (route, billable seconds, price multiplier) and payment (how you pay: credits_or_wallet or wallet).
  • 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 key aiw-videoedit-s is listed by GET /v1/pricing: GET /v1/pricing?model=aiw-videoedit-s. While the feature is disabled, GET /v1/pricing?model=aiw-* answers 404 for 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.fallback says so.

Routes ​

Responses use public route names only:

RouteUsed for
fotohub-video-edit-conversationalRestyle, replace and adjust ("make it look like film", "turn the car red")
fotohub-video-edit-preciseAdd 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-extendExtend the clip forward or backward

Edit a clip ​

python
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 version
typescript
import { 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);
bash
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 ​

FieldTypeDescription
instructionstringRequired. 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_secondsintegerSeconds 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_ackboolSet to true when the quote says likeness_ack_required
request_idUUIDIdempotency 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}/revert with {"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_progress otherwise).

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. Send likeness_ack: true to 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 with 422 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 disclosure on 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 ​

StatuserrorMeaning
401authentication_requiredNo or invalid key
402insufficient_fundsWallet (or credits) too low; required_usd, balance_usd, shortfall_usd, topup_url
403feature_disabledNot enabled for your account yet
404session_not_found, turn_not_foundNot yours or gone
409turn_in_progress, session_archived, not_cancellable, session_clip_unknownState conflict
422invalid_source_path, clip_too_long, selection_required, invalid_selection, instruction_blocked, likeness_ack_required, aspect_not_supported, clip_too_shortFixable input; nothing charged
429ceiling_exceededStart rate, active turns or the daily ceiling; Retry-After; nothing charged
503backend_unavailable, store_unavailableTemporarily not offered; Retry-After; not charged