Infinite Studio
API

Make videos, songs, pictures, presentations and voices from your own tools

Everything the app does, from a script or a server: describe a scene, add pictures, get a short video with sound; say what a song is about, get it written and sung; keep a voice and have it say your texts. Everything made with a key counts on the key owner's week, exactly like in the app.

In a conversation instead of a script? The same studio is an MCP server: add it to Claude, ChatGPT or Cursor and the assistant does all of this for the person, on their account.

Your key

Create one on the app's Account screen (“API access”). It is shown once. Send it as a bearer token on every call:

Authorization: Bearer ip_xxxxxxxxxxxxxxxxxxxxxxxx

A key reaches the studio's trades only (https://api.infinitepeople.ai/v1/video/… and /v1/song/… today, the other trades as they come); it cannot change your subscription or make other keys. Revoke it from the same screen if it leaks — or let the key burn itself: DELETE /v1/studio/key with that key revokes it at once. The app itself runs on these very routes: whatever it can do, a key can do, with the same account, the same week and the same plan.

Make a video

curl -X POST https://api.infinitepeople.ai/v1/video/generate \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-h3-ref2va",
    "prompt": "Mia hails a taxi in the rain, shouting \"Taxi!\"",
    "reference_urls": ["https://example.com/mia.jpg"],
    "duration_s": 5,
    "enhance": "low",
    "review": false
  }'

The answer is the creation: {"id": "…", "status": "enhancing", "ready": false, "keep_polling": true, "poll": "https://api.infinitepeople.ai/v1/video/jobs/…", "next": "Fetch 'poll' right now…", …}. Do not wait or sleep: fetch poll right away — it holds the connection up to 25 seconds and answers as soon as something changes (?wait=0 answers at once; GET or POST alike):

curl https://api.infinitepeople.ai/v1/video/jobs/$JOB_ID -H "Authorization: Bearer $KEY"

One stop signal: ready: trueresult.url is the mp4 (kept until expires_at — the account's retention, a year by default; a video kept in a project stays for good), result.poster_url its first frame, result.last_poster_url its last one — or keep_polling: false with a code (generation_failed, cancelled). next always says what to do; page_for_your_human is the creation in the app. The job's own fields (status, request, result_url…) stay as they were. A finished video also carries creation_id, retention, expires_at, and accessowner, edit or view (a video shared with you: POST /v1/creations/{creation_id}/shares by its owner, the link accepted at /v1/shares/{token}/accept) — with owner {id, name} when it is not yours; view opens the record and the files, edit also the gestures (upscale, sound, a new take), and what you make from it comes back to its owner shared for editing; a view-only share on a gesture answers 403 share_view_only; deleting is the owner's alone.

Two models

modelpicturesuse it for
minimax-h3-ref2vareference_urls (up to 4)people, places and styles the video draws on; cite them in the prompt as <Picture 1>, <Picture 2>… A voice_url (mp3, wav, m4a) gives the voice, cited as <Audio 1>.
minimax-h3-fl2vafirst_frame_url, optional last_frame_urlthe video starts on the first picture and lands on the second; no picture at all works too (text only). Pictures are brought to the clip's shape (see Shape).

Fields

fieldvalues
prompttextWhat happens: who, where, what moves. One sentence is enough. Written in any language; a line of dialogue in quotes is spoken as is.
duration_s5, 10, 15Up to 15 s, as the plan allows.
enhanceoff, low, medium, highThe direction help: off sends your prompt as is; low (default) rewrites it for the model; medium and high also paint the first image from your pictures or your text (high: twice as detailed). What your plan allows: GET /v1/video/melimits.enhance_levels.
definitionlow, high864×480 and 1344×768 at 16:9, the same pixel count at another shape (15 s at either). 768p is the ceiling of the public weights; a take you like is brought higher by the engine afterwards (below).
aspect16:9, 9:16, 1:1, 4:3, 3:4, 21:9, autoThe clip's shape; auto follows the first frame given, 16:9 without one.
fitauto, crop, bars, extendA start or end picture of another shape than the clip's: auto (default) crops it when close, sets it on black bars when far; crop and bars force one; extend has gpt-image continue the scene past its edges (a cloud step, a few cents on the week; with a subscription). To keep a picture's own shape: aspect auto.
confidentialitylow, medium, highWhere the work may run (default: the account's setting, low). low: the best tool wherever it is. medium: our machines and contracted providers that never train on your content, results stored in Europe. high: our own machines only — not open yet; GET /v1/studio/meconfidentiality.levels says what each level allows today.
qualitydraft, standard, finedraft: the sparse attention from 60 % of the schedule (and the 4-step schedule at 768p) — about 40 % less time (a 5 s clip: 18 s in 480p, 36 s in 768p), a hair less micro-detail on skin and hair, the same take to the eye; the rung to search on. standard (default): what the studio delivers (31 s, 59 s). fine: the full 20-step schedule, about twice the week of standard, for a take you already like — not for searching. An upscale renders with standard.
audiotrue / falseThe soundtrack, on by default.
reviewtrue / falseWith a painted first image, the app shows it for 10 s before rendering. From a script, send false: the job goes straight to rendering.
seedintegerFix it to reproduce a take.
negative_prompttextWhat to steer away from.

Your own pictures

A picture must be reachable by URL. If it is not hosted anywhere, send it first (JPEG, PNG or WebP, 8 MB max); the URL lives 7 days:

curl -X POST https://api.infinitepeople.ai/v1/video/inputs \
  -H "Authorization: Bearer $KEY" -F "[email protected]"
# → {"url": "https://media.infinitepeople.ai/ephemeral/inputs/…jpg"}

Pictures kept in a project (GET /v1/projects) have permanent URLs you can reuse directly.

Shape

aspect: 16:9 (the default), 9:16, 1:1, 4:3, 3:4, 21:9, or auto: the shape closest to the first frame you give (a 4:3 picture gives a 4:3 video; a 3:2 one the 4:3 shape with a centred crop of a few percent, at most 15 % of one side — never a stretch). A definition keeps its pixel count whatever the shape (low 864×480 or 480×864 or 640×640 or 544×736…, high 1344×768 or 768×1344 or 1024×1024), every side a multiple of 32. 24 frames per second, 5 to 15 seconds. A start or end frame of another shape than the one asked follows fit: auto crops it around its centre when it is close (within ×1.6) and sets it on black bars when it is the other orientation, crop and bars force one or the other, extend has gpt-image continue the scene past its edges at any direction level (off included; the estimate's cost_usd counts one call per distinct picture; when gpt-image declines a picture the clip goes on with the auto rule and the job's request says so under enhanced.fallback). With a start or end picture, the direction help paints nothing: medium and high are a rewrite like low. POST /v1/video/estimate with the same body answers the geometry, seconds (the render), director_seconds (what the direction help adds before it, review included) and total_seconds, without launching anything.

What a video would take, before launching it

curl -X POST https://api.infinitepeople.ai/v1/video/estimate \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"model": "minimax-h3-fl2va", "prompt": "…", "definition": "low", "quality": "draft", "duration_s": 5}'

Same body as the launch, nothing is queued. The answer says the exact pixels and frames the request resolves to, seconds (the render), director_seconds (what the direction help adds before it), total_seconds, the share of your week it would take, and online — whether a machine that serves this model is up. Useful to compare the tiers before spending: the same request in low/draft and in high/standard answers 18 s and 65 s of render.

The first image under review

With enhance: medium or high and review: true, the job stops in status review for ten seconds with its painted first image (request.enhanced.painted_urls[0]), then renders on its own. Act on it meanwhile:

curl -X POST https://api.infinitepeople.ai/v1/video/jobs/$JOB_ID/review \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"action": "approve"}'      # or "pause", "resume", or "regen" with "panels": [1] to repaint it
# "regen" takes a "note" (what is wrong, a sentence): the painter follows it, and every earlier note still holds

Start again from a video

A finished video gives pictures to start from: poster_url (its first frame), last_poster_url (its last), or any moment:

curl -X POST https://api.infinitepeople.ai/v1/video/jobs/$JOB_ID/frame \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"t": 2.5}'
# → {"url": "https://media.infinitepeople.ai/ephemeral/inputs/…jpg", "t": 2.5}

Use that URL as first_frame_url (a sequel), last_frame_url, or in reference_urls; add "edited_from": "$JOB_ID" to keep the lineage, and the same seed for the same take. A cancelled job: DELETE /v1/video/jobs/$JOB_ID (free before the render; counted once a node has it, the answer says charged). The same call puts a finished video in the bin: 48 h to take it back (POST /v1/creations/{creation_id}/restore, the id is in the answer), then its files are purged.

Bring a take to a higher definition

A finished video is rendered again by the engine at a higher definition, the video itself driving the render, so you get the same take (a new render at another definition would be another take: the seed only reproduces a video at the same geometry). Search in draft, then bring the take you keep up:

curl -X POST https://api.infinitepeople.ai/v1/video/jobs/$JOB_ID/upscale \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"definition": "high"}'
# → a new creation, "edited_from": "$JOB_ID", "request.upscale_from": {…}; poll it like any other

The engine follows the source frame by frame, but it paints every pixel again: small alterations can happen (a strand of hair, the skin's grain, a micro-expression). It comes out with two soundtracks on the same picture: the source's, kept bit for bit (what result.url plays), and the engine's own — result.sounds lists both, POST /v1/video/jobs/$JOB_ID/sound {"id": "new"} makes result.url play the other (what a project keeps, what the bench places). It counts on the week like a render at that definition times 1.45 (the video guides every step); the plan must allow the definition; 422 not_higher when the definition is not above the video's, 409 not_done on a video not finished. The source must still be there (its own expires_at).

1080p is another gesture on the same route: {"definition": "full_hd"} brings the finished video itself to 1080 pixels on its short side (1944x1080 from a 16:9 480p clip, its shape kept), frame by frame, with the detail the upscaler reconstructs (skin, hair, teeth) — the take, its motion and its soundtrack stay exactly what they were, one file, no second soundtrack. Two engines, chosen with quality. fast is the default and what to use: the picture is reconstructed — skin, hair, teeth — for about 20 s plus a fifth of the clip's length of machine (a 5 s clip: 25 s, a 15 s one: 35 s). fine is the same gesture with a touch more grain, for about six times the week of fast (2 min 40, 8 min): ask for it when someone wants the finest on a take they keep, not to see what an upscale looks like. Any MiniMax definition can go there, a video already in full_hd goes no higher. It runs on the 1080p machines, not the render ones, and counts on the week at its GPU time; for the take you keep, not every take. Paid plans; limits.definitions of GET /v1/video/me lists full_hd when the plan has it.

Another take

A finished video you almost like: the same request rendered again with a new seed — the prompt and the first picture as the direction help left them, the same settings, nothing prepared or paid twice:

curl -X POST https://api.infinitepeople.ai/v1/video/jobs/$JOB_ID/retake -H "Authorization: Bearer $KEY"
# → a new creation, "edited_from": "$JOB_ID"; poll it like any other

It counts on the week like a render at that definition; 409 not_done on a video not finished, 409 no_take on a 1080p enlargement (ask a take of the video it was made from). On a video shared with you for editing, the take is yours and comes back to its owner shared. To change something in the request, send it again.

Render again after a failure

A job in status failed (the week got it back) is rendered again as it went to the render, the direction help's work kept and the same seed, so nothing is prepared or paid twice:

curl -X POST https://api.infinitepeople.ai/v1/video/jobs/$JOB_ID/retry -H "Authorization: Bearer $KEY"
# → a new creation, "edited_from": "$JOB_ID"; poll it like any other

It counts on the week like a launch; a job that did not fail answers 409 not_failed. The pictures of a request live 7 days: past that, send the request again.

Your videos, a word on one

GET /v1/video/jobs lists the account's videos newest first (the ones under way, then the finished ones for as long as they stay), then those shared with it (each with access and owner); ?limit= caps the list (200 at most). Each item is shaped like GET /v1/video/jobs/{id}; what is in the bin is not listed (GET /v1/creations?trash=1).

A word on a finished video, for us: POST /v1/video/jobs/{id}/feedback {"rating": 1..5, "comment": "…"} — one per account and per video, sending again replaces; GET the same path reads yours. GET /v1/video/queue/stats gives the depths of the queue (how many wait, how many render) when you want a hint before launching; POST /v1/video/estimate (above) says what one request would take.

See Projects to keep a video or a picture for good, and Assemble a montage to put clips together.

Make a song

A song is made in three steps: a quiz (optional) makes it personal, the director writes the words (and a judge reads them), you send the draft (edited or not) to be sung. One take per call; songs are kept 7 days like videos. Every take is a creation: fetch poll (it holds the connection up to 25 s and answers as soon as something changes; ?wait=0 answers at once), stop when ready is true or keep_polling is false; next says what to do.

1. The quiz

Fifteen short questions whose answers change the words the most (names, places, memories, the tone), three rounds of five, each with three choices and a free field; the first asks the style when the request names none. Streamed as they are written (the first within two seconds):

curl -N -X POST "https://api.infinitepeople.ai/v1/song/questions?stream=1" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"request": "une chanson pour les 30 ans de Léa, qui adore voyager"}'
# {"id": "r1q1", "round": 1, "text": "Quel style pour Léa ?", "choices": ["Pop lumineuse", "Chanson festive", "Folk acoustique"], "free_text": true, "placeholder": "Angèle"}
# …one line per question…
# {"done": true, "next": "Send the answers to /v1/song/write as 'answers' [{question, answer}]."}

Without stream, the whole list at once. Show five at a time (by round), add a "skip" yourself, and pass what was answered to /write as answers. A light cloud call, for subscribers.

2. The words

curl -N -X POST "https://api.infinitepeople.ai/v1/song/write?stream=1&review=1" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"request": "une chanson pop pour les 30 ans de Léa, qui adore voyager, de Lisbonne à Tokyo",
     "answers": [{"question": "Quel style pour Léa ?", "answer": "Pop lumineuse"}]}'
# {"title": "Trente escales", "language": "fr", "style": "Angèle-like: first person, short lines, …"}
# {"line": "[Verse - intimate]"}
# {"line": "Elle a des cartes au fond des poches"}
# …one line per line of the lyrics, as they are written…
# {"draft": {"title": "…", "lyrics": "…", "caption": "pop, …, french", "duration_s": 170, "bpm": 118, "keyscale": "G major", "timesignature": 4, "words": 210, "instrumental": false}}
# {"fix": {"n": 7, "was": "Vers la maison", "now": "Vers l'horizon", "why": "trains / maison ne riment pas"}}
# …one line per line the judge changes…
# {"done": true, "draft": {…the fixes applied…}, "director": {…the director's draft…}, "review": {"scores": {"rhymes": 7, "meter": 8, "structure": 8, "singability": 8, "story": 9}, "summary": "…", "fixes": 3}, "next": "…"}

Two voices write it: a songwriter streams the words (a style line first: the writing it imitates), a producer sets the sound apart — the caption in English tags and the beat — so the story never tints the style. Name an artist in the request and the caption is built on that act's sound (the words stay original). review=1 has the judge read the draft in the same answer: n counts the non-blank lines of the lyrics from 1, tags included, now: "" removes a line; the closing draft has the fixes applied and director is the draft before them. Without stream, the draft at once (?review=1 adds director and review with its issues). Nothing is queued: the draft is yours to keep, edit or send back. Cloud calls, counted on the week.

To rework a draft, send it again with notes (streams the same way):

-d '{"request": "…", "previous": {…the draft…}, "notes": "one more verse about Lisbon, a sadder chorus"}'

"instrumental": true asks for no words: the songwriter draws the piece with section tags only (one or two instruments and the dynamics of each part) and the draft carries instrumental: true for /generate. With "music": "$ELEMENT_ID" (an element made with hear, see below) the director writes in the spirit of that track: its style, tempo and key, your words. The judge alone: POST /v1/song/review {"draft": …} (?stream=1 streams the fixes), answering issues, scores, summary and revised.

3. The music

curl -X POST https://api.infinitepeople.ai/v1/song/generate \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"title": "Trente escales pour Léa",
"lyrics": "[Verse - intimate]\nElle a des cartes au fond des poches\n…",
"caption": "pop, festive, female vocal, acoustic guitar, synthesizer, warm, catchy chorus, french",
"language": "fr",
"duration_s": 170,
"bpm": 118, "keyscale": "G major", "timesignature": 4,
"mode": "fast",
"lane": "normal"
}'
# → {"id": "…", "status": "queued", "lane": "normal", "ready": false, "keep_polling": true,
#    "poll": "https://api.infinitepeople.ai/v1/song/jobs/…", "next": "Fetch 'poll' right now …", "estimate_seconds": 30}

When ready, result.url is the mp3, result.recipe_url a JSON next to it with the timed lyrics (lrc, one line per line of the lyrics, tags included) and what the song was made of, result.duration_s the measured length; ?complete=1 inlines the recipe. cover_url is the song's picture, painted from the title and the style while the song renders (subscribers; usually there before the song).

Before launching, POST /v1/song/estimate with the same body answers estimate_seconds (the render), wait_seconds (the queue ahead on that lane), total_seconds, modes (the render time of each mode for this request), and whether the plan and the week allow it — nothing is queued.

fieldvalues
lyricstext, 4096 chars maxSection tags on their own lines ([Verse], [Chorus], [Bridge]…), with one or two performance hints in English after a dash ([Chorus - anthemic], [Bridge - whispered]). Tags only for an instrumental.
captiontags, 512 chars maxThe style as comma-separated English tags: genre, mood, voice and its timbre, instruments, era and production, texture, structure, the language last. Not a sentence; reminiscent of <artist> points at a style.
duration_s30 to 240Any length; 3 min on the free week and Starter, 4 min from Standard. The words are paced to fit.
modefast, normalfast (default) writes the song's blueprint with the light planner, about 30 s for 3 min; normal with the large one, richer composition, about twice the time (with a subscription).
lanepriority, normal, fillThe queue: priority is served first and counts three times on the week, fill runs when a node is idle and counts a fifth less; normal by default. Every plan has the three lanes.
languagefr, en, es, it, de, pt, nl, ja, ko, zhThe language of the vocals.
bpm, keyscale, timesignature82, "D major", 4The beat; the director sets them, left out the planner guesses.
titletextKept on the job for your library.
instrumentaltrue / falseNo vocals, whatever the lyrics say.
seedintegerFix it to reproduce a take.
voice, musicelement idsReferences, below.
source, edita song id; repaint, extend, restyleThe editor, below.

Statuses are queued, in_progress, done, failed, cancelled; a refusal carries code (not_on_plan, week_over, too_many_active, invalid_request…), a detail sentence and retry. DELETE /v1/song/jobs/{id} cancels or deletes like a video. Five creations in progress at most per account; thirty drafts an hour. Your week and what your plan allows in the songs: GET /v1/song/me → limits.modes, limits.max_duration_s, limits.lanes, limits.director, limits.cover. The catalogue and the licences: GET /v1/song/models.

4. Elements: a voice, a music

A track of yours, split on our machine into its voice and its music, to reference in a new song. Send a file (mp3, wav, flac, m4a, ogg, aac, 40 MB at most) with your word that you hold its rights or that it is free of rights; or split one of your finished songs.

curl -X POST https://api.infinitepeople.ai/v1/song/elements \
-H "Authorization: Bearer $KEY" \
-F "[email protected]" -F "title=Rescuer" -F "rights_confirmed=true" -F "hear=true"
# → {"id": "…", "kind": "element", "status": "queued", "ready": false, "keep_polling": true,
#    "poll": "https://api.infinitepeople.ai/v1/song/elements/…", "estimate_seconds": 50}

curl -X POST https://api.infinitepeople.ai/v1/song/elements \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"from_job": "$SONG_ID"}'                      # one of your finished songs, no rights to confirm

When ready: result.voice (the voice alone), result.voice_ref (the 30 s where it sings the most, what a take uses), result.music (the track without the voice), result.heard (with hear: what the planner makes of the track — a description, bpm, keyscale, timesignature — for /write's music; about 25 s more; POST /v1/song/elements/{id}/hear adds it to a track split without it, as a new element). A separation of a 3 min track takes about 30 s of GPU. GET /v1/song/elements lists yours; GET /v1/song/elements/{id} holds the connection like a job; DELETE removes one with its files. Elements are kept 7 days.

Then, in /generate: "voice": "$ELEMENT_ID" gives the take that singer's timbre and manner (not the melody); "music": "$ELEMENT_ID" makes the take sing your words on that element's music (its structure, chords and rhythm; the length follows the music, the plan's limit applies; no planner, mode does not matter). Both together: your words, on that music, with that voice.

5. The editor

A finished song of yours, changed without starting over: "source": "$SONG_ID" and edit. Lyrics, caption, language and beat default to the source's; the answer is a new song with edited_from = the source, and the source's cover.

# a passage made again, with the words you send (the whole lyrics, changed where you want)
-d '{"source": "$SONG_ID", "edit": "repaint", "from_s": 62, "to_s": 80, "lyrics": "…"}'
# 20 seconds more at the end (add the lines that sing there)
-d '{"source": "$SONG_ID", "edit": "extend", "add_s": 20, "lyrics": "…\n\n[Outro - fade out]\n…"}'
# the same song in another style; fidelity 0..1 says how much of the original stays (0.5 by default)
-d '{"source": "$SONG_ID", "edit": "restyle", "caption": "80s synth-pop, drum machine, female vocal", "fidelity": 0.3}'

A repaint covers 3 to 90 seconds and leaves the rest untouched; an extension adds 5 to 60 seconds; the length of the result (the source's, plus add_s) must fit the plan. No planner in any of them: an edit takes about as long as a fast song.

Edit a picture

A picture of yours and your words: the engine reads the picture and paints a new one as you say, the person in it kept (the same face, features, age and build; everything else follows your words). No zones to draw. Optionally, a selection (the magic wand, a box, the lasso) restricts the change to it. A result stays as long as your account's retention says (retention and expires_at on the creation: a year by default, 48 h in the ephemeral mode, for good when permanent; the bin for 48 h after a deletion); the pictures you send, the masks and the engine's windows are tools' products, 7 days. The faces, the segmentation, the cutout and the enlargement run on our own machine (every account, the free week and after); the edit, the generation and the extension are cloud calls, counted on the week at what they cost (a plan with a week: the free week of Essentiel included; 402 trial_over once it is behind and nothing is subscribed).

1. Send the picture

curl -X POST https://api.infinitepeople.ai/v1/image/inputs \
  -H "Authorization: Bearer $KEY" -F "[email protected]"
# → {"url": "https://media.infinitepeople.ai/ephemeral/image/inputs/u42/….jpg", "width": 1536, "height": 2752, …}

JPEG, PNG or WebP, 8 MB at most; moderated on the way in; stored upright (a phone's portrait on its side with an orientation tag is turned, the dimensions answered are the upright ones). A project's reference works as well (its URL is already ours).

2. The faces

curl -X POST https://api.infinitepeople.ai/v1/image/faces \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"image_url": "$URL"}'
# → {"faces": 1, "boxes": [[1033, 48, 1712, 536]], "cached": false, …}

How many faces the picture holds and where (about a second the first time, remembered an hour). It decides the engine of an edit: a face goes to the engine that preserves subjects, none to the fast one. Optional: pass faces to the edit to skip this look, or leave it out and the edit looks itself.

3. The edit

curl -X POST https://api.infinitepeople.ai/v1/image/edit \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"image_url": "$URL", "request": "a professional LinkedIn portrait, studio light, plain background", "shape": "portrait"}'
# → {"id": "…", "status": "queued", "kind": "transform", "engine": "gpt-image-2.5-sunburst", "engine_quality": "medium",
#    "ready": false, "keep_polling": true, "poll": "https://api.infinitepeople.ai/v1/image/jobs/…",
#    "next": "Fetch 'poll' right now; it holds the connection up to 25 s …", "estimate_seconds": 20}

The answer is a creation: fetch poll (it holds the connection up to 25 s and answers as soon as something changes; ?wait=0 answers at once). When ready, result.url is the picture the engine painted, whole (result.faces says how many faces it saw, result.enlarged_by whether our upscaler brought a large picture back to its size); ?complete=1 adds the prompt the engine got. Edit again from result.url, one change per turn; to go back, edit from lineage.previous_url (each creation carries its lineage: the root picture, the version, the previous creation; GET /v1/image/jobs?root=<url> lists the versions of a picture, oldest first).

fieldvalues
image_urlone of our URLsThe picture: an input, a project's reference, a result.
requesttext, 1000 charsWhat the picture becomes, in your words. A new situation (a LinkedIn portrait, at the beach) or a change to this very picture (black and white, a hat): say it plainly, the person stays who they are.
shapesquare, landscape, portrait, wide, WxHThe result's shape when it differs from the picture's own (a portrait from a landscape).
referencesup to 4 of our URLsPictures giving the identity of a person or the look of an object.
facesa numberFrom POST /faces, to skip the look.
qualitylow, mediumThe mode. low (default) spends little: our own engine edits your picture (GPU seconds), gpt-image-2.5 at its low tier makes one from words. medium: gpt-image-2.5 medium for both, about two cents. auto and draft read as low, keep and high as medium.
mask_url / box / polygonoptional, oneThe mask mode (below).
directortrue / falseOn by default: the director looks at the picture and writes the engine's instruction from your words, naming what changes as it is in the picture, what the new thing looks like and what stays (a small cloud call; the record's director says which model answered and what it cost). Send false to give the engine your words as they are.
lanepriority, normal, fillHow the week counts it (×3, ×1, ×0.5); the plan's default when absent.
confidentialitylow, medium, highThe account's default when absent; high keeps the local tools only (no cloud engine).

Two modes (quality). low, the default, the one that spends little: a picture you edit goes to our own engine, Qwen-Image-Edit-2511 on our machine (engine_quality: local, counted in GPU seconds), and a picture made from words to gpt-image-2.5 at its low tier. medium: gpt-image-2.5 at its medium tier for both. Whatever the mode, when gpt-image-2.5 cannot answer the creation goes to qwen-image-3.0 on Alibaba Model Studio, and when our machine cannot take an edit it goes to the cloud engine of the mode — the job's engine, engine_quality and route_reason always say which one served. Between the two gpt engines: Sunburst, the one that preserves subjects, when the picture holds a face (faces, from POST /v1/image/faces) or a reference is given, or when nobody looked; Flare, the fast one, otherwise. The director writes the instruction from your picture and your words by default. POST /v1/image/estimate with the same body tells the engine, the seconds and what the week is asked, without launching anything; GET /v1/image/meservice.engine says whether our machine is up. (auto and draft read as low, keep and high as medium.)

The mask mode: only a selection changes

curl -X POST https://api.infinitepeople.ai/v1/image/edit \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"image_url": "$URL", "mask_url": "https://media…/masks/….png", "request": "an ambulance in place of the taxi"}'

With a selection — a mask from the magic wand (POST /v1/image/segment/open then /segment/tap {"session", "points": [[x, y]]}: the object under a tap, best mask first; /segment/concept {"text"} for every instance of a word; /segment/scene for everything that matters), a box [x0, y0, x1, y1] or a polygon [[x, y], …] (the lasso) in the picture's pixels — only the selection changes (kind: edit): the engine paints a window around it and the change is composited back into your picture, so nothing outside moves. A replacement takes exactly the place, size and perspective of what it replaces. result.window_url is the engine's own output of the window, result.mask_url the selection, result.used_mask_url what came back; fit says how: smart (default) keeps your picture as it is as far as possible — the selection plus what the engine clearly changed around it (a jacket asked over a t-shirt comes back whole; spill says by how much) — selection the selection alone, none the engine's whole window. A selection covering the whole picture is the whole picture. The masks come cleaned of the model's specks and inline (mask_png_b64) on a tap.

The object alone

curl -X POST https://api.infinitepeople.ai/v1/image/cutout \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"image_url": "$URL", "mask_url": "https://media…/masks/….png"}'
# → {"url": "https://media…/cutouts/….png", "width": 679, "height": 1488, "box": [1033, 48, 1712, 1536]}

A PNG with transparency, cropped to the object ("crop": false keeps the frame), a soft edge of feather_px (2 by default). Local, immediate, free of the week.

A picture from words

curl -X POST https://api.infinitepeople.ai/v1/image/generate \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"request": "a ceramic mug on a wooden table by a window at dusk", "shape": "landscape", "style": "a photograph"}'

A creation like an edit. shape: square (1024²), landscape (1344×768, default), portrait, wide (1536×640), or a WIDTHxHEIGHT on multiples of 16 (655 360 px at least, no side above 2048: enlarge afterwards). references (up to 4) give the identity of the people or the look of the objects in them, never the composition. The same quality, director, lane, confidentiality as an edit.

A wider frame

curl -X POST https://api.infinitepeople.ai/v1/image/extend \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"image_url": "$URL", "shape": "square", "request": "more of the room"}'

The scene continued beyond the edges into a new frame: a shape (the picture's longer side is kept) or a factor (1.05 to 2, both sides); result.box says where your picture sits, never scaled. request is optional.

The engine redraws the whole frame, your picture included: at its native size the redrawing keeps the composition but drifts by a few pixels and reinvents textures; a large photo it had to paint smaller comes back recomposed. So fit says how your picture comes back, as for an edit: smart (default) keeps it as it is as far as possible — the engine's pixels are taken where it clearly changed things, connected to the new bands, yours everywhere else, the seam where both agree (a flowerpot or a leg at the edge is not doubled); selection pastes your picture back whole, pixel for pixel (a seam can show where the engine drifted); none is the engine's frame as it is. result.kept is the share of your picture that came back untouched (1.0 = all of it; a large photo may keep a fifth), result.used_mask_url where the engine's pixels were taken, result.window_url its frame as it painted it. A frame painted smaller than asked is enlarged by our upscaler first (result.enlarged_by), not by a resize.

Larger

curl -X POST https://api.infinitepeople.ai/v1/image/upscale \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"image_url": "$URL", "factor": 2}'

×2, ×3 or ×4 on our own machine, every plan: a second or two for a megapixel, ten for a 4K result, counted in GPU seconds. No diffusion: a photo stays the same photo, larger (its file says AI-upscaled - 4xNomos - Infinicia in its metadata, like every file we produce). "mode": "fine" is a fidelity notch (a transformer, five times slower, more careful with a noisy or compressed photo; on a clean picture fast is as sharp). Source of at most 3840×2160.

Every creation of the trade is read on GET /v1/image/jobs/{id} and listed on GET /v1/image/jobs (a bare list: yours newest first, then the ones shared with you, each shaped like GET /jobs/{id}); DELETE /v1/image/jobs/{id} cancels one that is not done (counted if the engine was already called: the answer says charged) or puts a finished one in the bin (GET /v1/creations?trash=1, POST /v1/creations/{creation_id}/restore for 48 hours; a creation in the bin answers 404 in_bin with its creation_id). Every record carries creation_id, the registry's id, retention and expires_at, and access: owner, or the role a share gives you (POST /v1/creations/{creation_id}/shares {"role": "view" | "edit"} hands one person a one-time link; GET /v1/creations?shared=1 lists what is shared with you) with owner: {id, name}view reads the record and the picture, edit also lets you edit, widen or enlarge from its result.url (the new creation is yours, on your week, in the picture's lineage, and comes back to its owner shared for editing); a view-only share that launches gets 403 share_view_only, anyone but the owner who deletes 403 not_owner. Five in progress at most per account. Your week and what your plan allows in the pictures: GET /v1/image/melimits.edit, limits.qualities, limits.upscale. The catalogue: GET /v1/image/models.

code
404 session_expiredthe segmentation session is over (15 minutes) or not yours: open the picture again
503 segmentation_busythe segmentation card is busy for retry_after_s; a box or a polygon needs no model meanwhile
422 empty_maskthe mask selects nothing
job failed, refused_by_enginethe engine's safety layer refused the edit; the week was given back — change the request or the selection
job failed, unworkable_selectionthe selection is more than 3:1 within the picture; widen it
429 engine_busythe engine has done its share of pictures this hour for everyone; later
403 share_view_onlythe picture is shared with you for viewing only; its owner can give you edit access
403 not_owneronly the owner deletes a creation shared with you
404 in_binthe creation is in the bin; POST /v1/creations/{creation_id}/restore within 48 hours

Make a presentation

A page written by our own model, on our machines, from a sentence: slides, a one-page site, or a live quiz. The page is a single HTML file on our kit (themes, layouts, components, fonts), served by its id, sandboxed; it lives 7 days. Counted on the week in GPU seconds at the node's rate, every plan.

curl -X POST https://api.infinitepeople.ai/v1/deck \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"prompt": "An eight-slide pitch for investors: the problem, our answer, the market, the team, the ask.", "shape": "deck", "theme": "night"}'
# → {"id": "…", "status": "queued", "ready": false, "keep_polling": true, "poll": "https://api.infinitepeople.ai/v1/deck/jobs/…",
#    "next": "Fetch 'poll' right now; it holds the connection up to 25 s …", "estimate_seconds": 33, "version": 1, "root_id": "…"}

Fetch poll (held up to 25 s; ?wait=0 answers at once). When ready, result.url is the page — open it, share it, put it in an <iframe sandbox="allow-scripts">; result.title, result.shape and result.slides describe it; ?complete=1 adds the HTML itself as page. The page is public to whoever holds its address (the id is the secret).

fieldvalues
prompttext, 3 to 3000 charsWhat the presentation says, in your words: the subject, the audience, the tone, how many slides.
shapedeck, site, quizSlides to present; a one-page site to send; a quiz to play live (questions, answers, a scoreboard).
themenight, paper, ocean, cinema, kids, forest, warm, mono, candy, slateLeft out, our model picks one for the subject. The kit's palette: GET /v1/deck/kit.
templatean id of GET /v1/deck/kittemplatesA starting structure for the shape (a pitch, a lesson, a launch; a showcase, a report, an event; three quizzes). GET /v1/deck/kit/templates/{id} shows one as a page (?theme= to try a theme).
mediaup to 12 of {"url", "kind": "image" | "video" | "audio", "title"}Files of ours (a project's picture, a video, a song) our model may place; title is what it reads about each.
languagefr, enThe page's language; the prompt's when left out.
lane, confidentialityas everywhereThe presentations are written on our own machine: available at every confidentiality level, high included.

Rework it

curl -X POST https://api.infinitepeople.ai/v1/deck/jobs/$ID/edit \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"instruction": "Add a slide on pricing after the market, and make the accent orange."}'

A new creation in the lineage (edited_from, root_id, version n+1); the previous page stays. POST /v1/deck/estimate {"shape", "template?", "lane?"} tells the seconds and what the week is asked without launching; GET /v1/deck/jobs lists your presentations; DELETE /v1/deck/jobs/{id} cancels one (counted if our model was already writing: charged) or deletes a finished one. Your rights: GET /v1/deck/melimits.shapes, limits.media_max; service.online says whether our model is up.

code
422 invalid_requestan unknown shape, theme or template; a medium that is not on our media host
409 not_a_finished_pagethe page to rework is not finished yet
job failed, page_refusedour model could not make an acceptable page; the week was given back — rephrase
job failed, engine_unavailableour model's node is down; try again in a minute
job failed, source_missingthe page to rework has expired

Give a voice

A voice kept on your account — yours, recorded, or one designed from words — that says your texts and takes over your recordings. Everything runs on our own machine: saying at 48 kHz (VoxCPM2), converting at 24 kHz (CosyVoice 3). Every plan, counted on the week in GPU seconds; what a creation makes is kept as long as your account's retention says (a year by default; retention on the request chooses for one creation; deleting puts it in the bin for 48 hours), a kept voice until you delete it. A text is moderated before it is said.

1. Keep a voice

curl -X POST https://api.infinitepeople.ai/v1/voice/voices \
  -H "Authorization: Bearer $KEY" \
  -F "name=Nicolas" -F "[email protected]" -F "rights_confirmed=true" \
  -F "transcript=Bonjour, je m'appelle Nicolas. Je lis ce texte pour donner ma voix au Studio…"
# → {"id": "61e7…", "name": "Nicolas", "kind": "recorded", "sample_url": "https://media.infinitepeople.ai/voices/library/u42/….m4a",
#    "transcript": "Bonjour, je m'appelle Nicolas. …", "duration_s": 28.8, "faithful": true, "next": "Say a text with it: …"}

A recording of one person speaking, 3 to 60 s (the engine reads the first 30; ten to thirty of natural reading is best), wav, mp3, m4a, aac, ogg, webm or flac, 40 MB at most, with the word on the rights: rights_confirmed says it is your voice, or you hold the rights on it. Give transcript, the exact text read, when you have it: the voice is then faithful — the engine continues your recording, the closest cloning (a swallowed word or a hesitation is fine; a text read in part is not: a transcript that does not fit the recording's length is set aside, and next says so). Without it, an engine makes one (qwen3-asr-flash on Alibaba Model Studio, Whisper as the fallback: a cloud call, counted on the week at its cost; not at the high confidentiality level, not once the free week is behind without a plan); otherwise the voice is cloned from its timbre alone (faithful: false), still the same voice, a little freer. language (fr, en…) helps the transcript; gender (female | male) says the voice's register, for a mood's actor.

curl -X POST https://api.infinitepeople.ai/v1/voice/voices \
  -H "Authorization: Bearer $KEY" \
  -F "name=Conteuse" -F "description=a woman in her forties, warm low voice, a little amused, a Parisian accent" -F "language=fr"
# → {"id": "1193…", "kind": "designed", "sample_url": "…/voices/library/u42/….wav", "transcript": "Bonjour, voici ma voix. …", "duration_s": 13.0, …}

Or a voice from words, drawn several times: POST /v1/voice/designs {"description": "une femme d'une cinquantaine d'années, voix grave et lente", "language": "fr", "takes": 3} answers one take per draw (seed, duration_s, sample_url), each saying the same sample of about fifteen seconds. The draw decides, not the language: the same words give a take that obeys and one that drags, so listen and keep the one you like — POST /v1/voice/voices with name, its design_url and the same description, and it is kept exactly as you heard it (nothing rendered again, nothing counted twice). The whole draw is counted once on the week (a few GPU seconds a take); the takes nobody keeps go with the week's cleaning. One shot still works (description straight to /voices, seed to repeat a draw). Thirty voices per account; GET /v1/voice/voices lists them (yours, then the ones shared with you), GET …/voices/{id} is one, PATCH …/voices/{id} {"name"} renames it ({"gender"} sets its register), DELETE …/voices/{id} puts it in the bin (48 hours to take it back, GET /v1/creations?trash=1; then its sample is gone; the creations made with it stay). A kept voice is a creation of the registry too (creation_id on its record, kind voice, kept for good): share it like any creation — POST /v1/creations/{creation_id}/shares {"role": "view" | "edit"} makes a one-time link; the person who takes it hears the voice (view) or says texts with it and converts recordings into it (edit, on their own week); every record then says access and owner. What is said in a shared voice comes back to the voice's owner, shared for editing: you hear everything made with your voice. Withdraw a share at any time (DELETE …/shares/{share_id}); the bin ends them all.

2. Say a text

curl -X POST https://api.infinitepeople.ai/v1/voice/say \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"voice": "61e7…", "text": "Bonjour à tous, et bienvenue dans Infinicia.", "seed": 11}'
# → {"id": "…", "kind": "say", "status": "queued", "ready": false, "keep_polling": true, "poll": "https://api.infinitepeople.ai/v1/voice/jobs/…",
#    "next": "Fetch 'poll' right now; it holds the connection up to 25 s …", "estimate_seconds": 2}

A creation: fetch poll (held up to 25 s; ?wait=0 answers at once). When ready, result.url is the speech (WAV, 48 kHz, result.duration_s long), retention and expires_at how long it stays, result.seed the seed it used: the same text with the same seed says it the same way, another seed is another take. A long text is fine (6 000 characters at most, about a minute of speech for 900): the engine says it by sentence groups, each continued from the voice and the previous group, so the voice holds from the first line to the last; a minute of speech renders in fifteen to twenty seconds. denoise cleans a noisy recording before cloning (a phone in a street). The delivery is the voice's own — unless you ask a mood: joy, sadness, calm, whisper, enthusiasm or tenderness (anger is not offered: the engines do not play it well). The text is then played by an actor designed in that mood, of the voice's register (gender on the kept voice: given, or read in a designed voice's description), and converted into the voice: same words, the actor's intention, your voice's timbre (24 kHz, about twice the GPU seconds of a plain take).

3. Play it yourself: the conversion

curl -X POST https://api.infinitepeople.ai/v1/voice/convert \
  -H "Authorization: Bearer $KEY" \
  -F "voice=1193…" -F "[email protected]" -F "rights_confirmed=true"
# → a creation; when ready, result.url is the recording said again in the voice (WAV, 24 kHz)

Record the line yourself, with the pace and the intention you want; the kept voice takes it over word for word, same rhythm, same intention — the way to have an emotion in a voice. The source comes as file (with rights_confirmed: it is you, or you hold the rights), as file_url on our media host (a song's voice element, a project's asset; no word needed), or as source, a finished creation of the trade — yours, or one shared with you for editing (edited_from on the new record). Two minutes at most; about 0.6 s of GPU per second of recording. steps (4 to 64, 30 by default) is the conversion's diffusion steps: fewer is faster, 30 is cleaner on expressive sources.

fieldvalues
voicean id of GET /v1/voice/voicesThe kept voice that says, or takes over.
texttext, 1 to 6000 charsWhat is said (say only).
seedintegerThe same seed, the same take (say and design).
moodjoy, sadness, calm, whisper, enthusiasm, tendernessThe delivery, played by an actor and converted into the voice (say only; 24 kHz).
file / file_url / sourcea recording / one of our URLs / a creation's idThe source to convert (convert only); a file needs rights_confirmed; a creation is yours or shared with you for editing.
lanepriority, normal, fillHow the week counts our GPU (×3, ×1, ×0.8); every plan has the three; normal by default (priority on Pro, at cost). The voices run on our machine, so the lane is the whole of it; a cloud call (a recording's transcript) is counted at its price whatever the lane.
confidentialitylow, medium, highSaying, designing and converting run on our own machine: available at every level. At high the transcript (a cloud call) and the moderation of a text are out, so a voice is kept from its timbre and a text cannot be said yet; converting works.
retentionstandard, ephemeral, permanentHow long this creation stays (a year; 48 hours then gone for good; until deleted); the account's default when absent. Kept in a project, it does not expire meanwhile.

Every creation of the trade is read on GET /v1/voice/jobs/{id} (?complete=1 adds the voice's transcript as sent) and listed on GET /v1/voice/jobs (a bare list: yours newest first, then the ones shared with you); DELETE /v1/voice/jobs/{id} cancels one that is not done (counted if the node was already working: charged) or puts a finished one in the bin (GET /v1/creations?trash=1, POST /v1/creations/{creation_id}/restore for 48 hours; a creation in the bin answers 404 in_bin with its creation_id). Every record carries creation_id, the registry's id, and access: owner, or the role a share gives you (POST /v1/creations/{creation_id}/shares {"role": "view" | "edit"} hands one person a one-time link; GET /v1/creations?shared=1 lists what is shared with you) with owner: {id, name}view reads the page and the file and keeps it in a project of yours, edit also converts it into a voice of yours (the conversion is yours, on your week, and comes back to its owner shared for editing); a view-only share that converts gets 403 share_view_only, anyone but the owner who deletes 403 not_owner. Five in progress at most per account. Your week and what your plan allows: GET /v1/voice/melimits.transcript, limits.max_text_chars, limits.max_source_s. The engines: GET /v1/voice/models.

code
422 rights_requireda recording sent without rights_confirmed
422 moderation_refusedthe name, the description or the text was refused
409 too_many_voicesthirty voices are kept already; delete one
503 voice_busythe voice card belongs to a character generation for retry_after_s (a design is immediate: call again then; a creation waits by itself)
job failed, voice_unavailablethe node is unreachable; the week was given back — send it again in a minute
job failed, invalid_inputthe recording could not be read, or the text could not be said

Projects

A project keeps for good what the studio makes — pictures, videos, songs and elements (a voice, a music), texts said and recordings converted — and the montages assembled from them; everything else lives 7 days. Every item has a handle (picture:5, video:12, song:3, element:7:voice): the word the bench takes.

curl https://api.infinitepeople.ai/v1/projects/$PROJECT -H "Authorization: Bearer $KEY"
# → {"id": 12, "name": "Mia",
#    "references": [{"id": 5, "handle": "picture:5", "url": "https://media.infinitepeople.ai/videos/references/u42/….png"}],
#    "videos": [{"id": 12, "handle": "video:12", "title": "The yellow taxi under the rain", "url": "…/videos/projects/u42/….mp4", "poster_url": "…", "duration_s": 5.0, "job": {…}}],
#    "songs": [{"id": 3, "kind": "song", "handle": "song:3", "title": "Trente bougies", "files": {"url": "…"}, "duration_s": 170},
#              {"id": 7, "kind": "element", "handles": {"voice": "element:7:voice", "music": "element:7:music"}, "title": "Léa", "files": {"voice": "…", "music": "…"}, "duration_s": 182}],
#    "montages": [{"id": 4, "name": "Night", "version": 9, "duration_s": 23.5, "last_render": {…}, "page_for_your_human": "https://infinicia.ai/#montage:12:4"}], "montage_count": 1,
#    "page_for_your_human": "https://infinicia.ai/#project:12", "next": "…"}

GET /v1/projects/{id}/assets answers everything the project keeps with the same words for each — handle, trade, job_id, kind, title, urls by role, duration_s, and how long it stays (retention, held, expires_at, creation_id) — pictures you uploaded (retention: "project") included. An edited picture the project keeps can start a shot on the bench ("picture": "image:4").

Limits: 30 projects on an account; 60 pictures, 30 videos, 60 songs and elements, 60 voices, 20 montages in a project (409 project_full).

Assemble a montage

The bench. A montage is a document of a project — a sequence of video clips with a transition between each, a voice track and a music track placed in time — that you change by operations and export as an mp4. What the app's bench does, you do here with the same words.

A montage is named by its id alone (/v1/montages/{id}/…); it lives in a project — POST /v1/projects/{id}/montages — or without one: POST /v1/montages {"name"} puts it in the account's inbox, a project of kind inbox (one per account, neither renamed nor deleted) where what the montage uses is kept for good like anywhere else. GET /v1/montages lists every montage of the account with its project; PATCH /v1/montages/{id} {"project_id", "name"?} moves one into a project, its clips keeping their files.

curl -X POST https://api.infinitepeople.ai/v1/projects/$PROJECT/montages \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"name": "Night", "aspect": "16:9"}'
# → {"id": 4, "version": 1, "doc": {…}, "summary": {"duration_s": 0, "video": [], "voice": [], "music": []},
#    "palette": [{"handle": "video:12", "kind": "video", "title": "The yellow taxi under the rain", "duration_s": 5.0, "url": "…"},
#                {"handle": "song:3", "kind": "music", "title": "Trente bougies", "duration_s": 170, "url": "…"},
#                {"handle": "element:7:voice", "kind": "voice", "title": "Léa", "duration_s": 182, "url": "…"}],
#    "page_for_your_human": "https://infinicia.ai/#montage:12:4",
#    "next": "Empty montage, version 1. Place clips with POST …/ops {\"ops\": [{\"op\": \"add\", \"track\": \"video\", \"from\": \"video:<id>\"}], \"version\": 1} …"}

Then operations, applied in order, all or none, one save:

curl -X POST https://api.infinitepeople.ai/v1/montages/4/ops \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"version": 1, "ops": [
  {"op": "add", "track": "video", "from": "video:12"},
  {"op": "add", "track": "video", "from": "job:2f3a5b7c-…", "out": 3},
  {"op": "add", "track": "music", "from": "song:3", "start": 0, "out": 8, "value": 0.5},
  {"op": "transition", "id": "c1a2b3", "kind": "fade", "s": 0.5}]}'
# → {"id": 4, "version": 2,
#    "done": ["'Rain at night' kept in the project", "'The yellow taxi under the rain' added to the video track at place 1", "'Rain at night' added to the video track at place 2", "'Trente bougies' added to the music track at 0 s", "'The yellow taxi under the rain' now fades into the next clip over 0.5 s"],
#    "summary": {"duration_s": 7.5, "video": [{"id": "c1a2b3", "title": "The yellow taxi under the rain", "start": 0, "length": 5, "transition": "fade", "from": "video:12"}, {"id": "c4d5e6", "title": "Rain at night", "start": 4.5, "length": 3, "transition": "cut", "from": "video:13"}], "voice": [], "music": [{"id": "a7b8c9", "title": "Trente bougies", "start": 0, "length": 8, "gain": 0.5, "from": "song:3"}]},
#    "doc": {…}, "next": "Version 2: send it as 'version' with your next operations …"}
opfields
addtrack video / voice / music; from a handle (or src, a source copied from the palette); in, out seconds into the source (out defaults to its length); start audio: seconds on the montage's clock; index video: its place, 0-based; value audio: volume 0..2 (music 0.6 by default)a clip on a track
removeid
moveid; index (video) or start (audio)
trimid; in, outseconds into the source
splitid; at seconds on the montage's clockthe clip becomes two
transitionid; kind cut / fade; s secondsfrom this video clip to the next
gainid; value 0..2voice or music
fadeid; fade_in_s, fade_out_svoice or music
renameid; title
replaceid; from (or src)another source in the same place, bounds and transition kept
aspectaspect 16:9, 9:16, 1:1, 4:3, 3:4, 21:9

Handles: video:12, song:3, element:7:voice, element:7:music name what the project keeps (GET /v1/projects/{id}); job:<uuid>, song:<uuid>, element:<uuid>:voice name a finished creation of the account, kept in the project on the way, so the montage never points at a file that expires. Video clips play one after the other in the order of summary.video; a fade pulls the next clip back by its length. Voice and music clips are free: each has a start. Every other operation names its clip by the id shown in summary.

version is optional but wise: the version you read, sent back, makes a change made elsewhere meanwhile a 409 montage_stale (the body says the current version) rather than a silent overwrite. GET /v1/montages/{id} reads the montage again with summary and palette; PUT /v1/montages/{id} {"doc", "version"} replaces the whole document; DELETE /v1/montages/{id} removes it with its exports.

A sentence instead of operations: POST /v1/montages/{id}/direct {"prompt": "put Trente bougies under the whole montage and cut the taxi at 3 s"} — our director turns it into operations, applied all or none; done says each, reply its word, refused why not. It costs a small cloud step on the week; the operations you send yourself are free.

A video that does not exist yet: POST /v1/montages/{id}/make {"shots": [{"prompt": "Mia hails a yellow taxi in the rain", "duration_s": 5, "index": 0, "picture": "picture:5", "title": "The taxi"}]} — up to 5 shots, each a video creation on the week like any other (the montage's frame; picture starts it on a picture of the project; index its place, the end when absent), placed on the video track by itself when ready; until then the montage's pending lists them with their poll, and done says when one landed or could not be made. The director does the same from a sentence (“add a shot of the beach at dawn after the taxi”): its answer carries the shots in make, not made until you send them to …/make — or pass "make": "launch" to /direct to make them at once.

Sharing (the studio's rule for every creation): an export is a creation — POST /v1/creations/{creation_id}/shares {"role": "view" | "edit"} hands one person a link; taken, it opens the montage to them: GET /v1/montages/{id} answers its card with access, owner and shared_renders (with the document for edit), GET /v1/montages/{id}/renders/{rid} the export to watch and download; GET /v1/montages lists the montages shared with you after your own. With edit, POST /v1/montages/{id}/fork {"name"?, "project_id"?} makes your own montage from it (your week pays its exports, which come back to the owner shared for editing); the owner's montage itself is changed by the owner only (403 not_owner, share_view_only for a viewer).

Export: POST /v1/montages/{id}/render answers a creation (poll, ready, keep_polling, next); ready, result.url is the mp4 and result.poster_url its first frame, kept with the project (last_render on the montage). Limits: 40 clips, 5 minutes.

Touch up a clip's picture, as the app's object tool does: take the frame (POST /v1/video/jobs/{job_id}/frame {"t"}), edit it (POST /v1/image/segment/open, /tap, /edit), make the clip again from the corrected picture (POST /v1/video/generate with first_frame_url and "edited_from"), then {"op": "replace", "id": "c1a2b3", "from": "job:<the new video>"}.

Statuses

status
enhancingwaiting for, or under, the direction help
reviewthe painted first image awaits your say (review: true); it goes on by itself after review_deadline
queuedin line for a rendering node
in_progressrendering; estimate_seconds is the expected render time
doneresult_url is ready
failed, cancelledthe week gets the video back (unless cancelled while rendering); POST …/jobs/{id}/retry renders a failed one again

DELETE /v1/video/jobs/{job_id} cancels a job (free before the render, counted once a node has it) or puts a finished video in the bin (48 h to restore it, POST /v1/creations/{creation_id}/restore).

Refusals

Every refusal is a status and a JSON body: detail is a sentence that says what to do and whether retrying makes sense, code a stable word, retry a boolean (with retry_after_s when waiting helps): {"detail": "This account's week is used up…", "code": "week_over", "retry": false, "resets_at": "…"}. A 4xx without a JSON body is not ours.

code
402 not_on_planthe length or the direction level is not on your plan
402 week_overyour week is spent; resets_at says when it starts again
422the request or a picture was refused by moderation, or a field is wrong (invalid_request: the detail says which)
422 not_available_at_confidentialitythe confidentiality asked (low by default, medium, high) cannot do a step of the request; refused names the option to change (the direction help needs a cloud service) — high is not open yet
404 not_foundnot there, or not yours: a project, a montage, a handle, a job
409 montage_stalethe montage changed since the version you sent; version says the current one — read it again
409 project_fullthe project holds its most of that (pictures, videos, songs, montages)
429ten videos already in progress on the account
503the service is paused for maintenance; try again later

Tell us

A bug, a wish, a word on a creation: POST /v1/studio/feedback with {"subject": "api" | "video" | "song" | "image" | "deck" | "voice" | "app" | "other", "message": "…", "job_id": "…"} (the job is optional). We read every one. The rating of a video (1 to 5 and a comment) stays on POST /v1/video/jobs/{job_id}/feedback.

For machines

The OpenAPI document of every route a key can reach: api.infinitepeople.ai/v1/studio/openapi.json. This page in Markdown: /api/index.md. A summary for language models: /llms.txt.

MCP. The same routes as tools, from a conversation: https://api.infinitepeople.ai/v1/mcp/crea (JSON-RPC 2.0 over HTTP, POST only, your key as the bearer). The tools are named verb_object (list_projects, get_project, keep_video, create_montage, edit_montage, direct_montage, render_montage, make_video, get_video, write_song, make_song, get_song, split_song…), one parameter per field of the route, and answer what the route answers (a refusal is a result with isError). In Claude Code:

claude mcp add --transport http infinicia https://api.infinitepeople.ai/v1/mcp/crea \
  --header "Authorization: Bearer $KEY"

Then say what you want: “make a 5-second video of a yellow taxi in the rain, keep it in my project Mia, put it on a new montage with my song Trente bougies under it, and export”. Any MCP client that sends a bearer header works the same way.

Add Infinicia to Claude (claude.ai, desktop, mobile). No key to paste: in Claude, open Settings › Connectors › Add custom connector, give it the URL https://api.infinitepeople.ai/v1/mcp/crea, then connect — a page of the studio opens, you sign in with Google, and Claude holds a key of your account (scope studio, named “Claude (connecteur)”, shown and revocable in the app under Account › API access; it renews itself, and everything Claude makes counts on your week as in the app). Claude Code and other MCP clients that speak OAuth 2.1 (PKCE S256, dynamic registration or a client metadata document) go through the same door: https://api.infinitepeople.ai/.well-known/oauth-authorization-server says where.

Your week

curl https://api.infinitepeople.ai/v1/video/me -H "Authorization: Bearer $KEY"

week.left_pct is the share of your week that is left, week.resets_at when it starts again, limits what your plan allows. The catalogue with every field: GET /v1/video/models.

The whole account in one read: GET /v1/studio/meuser (your name, your picture's URL), plan (Gratuit, Essentiel, Starter, Standard, Pro), week, limits and services per trade, retention (what your next creations get: standard, a year; ephemeral, 48 hours; permanent). Your name as others see it on a shared creation: POST /v1/studio/profile {"name": "…"} (2 to 40 characters); your picture: POST /v1/studio/avatar (multipart file, JPEG, PNG or WebP up to 5 MB, cropped to a square) and DELETE /v1/studio/avatar. The plans and their prices: GET /v1/studio/plans?currency=EUR.

Videos are generated by MiniMax H3 and brought to 1080p by SwiftVR or SeedVR2, songs by ACE-Step 1.5, the segmentation by SAM 3, the enlargement by Philip Hofmann's Nomos upscalers, the voices by VoxCPM2 (OpenBMB) and their conversion by CosyVoice 3 (FunAudioLLM) on our machines; the direction help and the lyrics are written by Qwen on Alibaba Model Studio (OpenAI as the fallback), the painted first image and the picture edits in the cloud by gpt-image-2.5, the transcript of a voice by Whisper (OpenAI). Every request is moderated before it runs. The terms of use apply to what you make with a key as much as in the app.