Infinicia
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: true — result.url is the mp4 (kept until expires_at — the account's retention, while it is covered by default; a video kept in a project is not deleted for want of use), 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 access — owner, 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/me → limits.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 a first render; a take you like is brought higher 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 the scene continued past its edges (a step at a partner under contract, a few cents on the week; with a subscription). To keep a picture's own shape: aspect auto.
confidentialitylow, highWhere the work may run (default: the account's setting, low). low: our machines and contracted providers that never train on your content (medium, the level of before, reads as low). high: our own machines only, with the Standard and Pro plans (402 not_on_plan otherwise); GET /v1/studio/me → confidentiality.levels says what each level allows today.
qualitylow, medium, highmedium (default): what the studio delivers. high: the full 20-step schedule, about twice the time of medium (a 5 s clip at 768p: about 2 min against 53 s), for a take you already like. low, the turbo of 768p (the 4-step schedule and the sparse attention): 25 to 35 % less time, but another take that follows the start image less closely — offered, never the default; at 480p it renders as medium (it saved no time there). An upscale renders with medium. The words of before, draft, standard and fine, are still read.
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 (or a voice sample) must be a file on our media host, https://media.infinitepeople.ai: any other address is refused before it is read (422 not_our_media). Send yours 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 the scene continued past its edges at any direction level (off included; the estimate's cost_usd counts one call per distinct picture; when a picture is declined 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", "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 before spending: the same 5 s request in 480p and in 768p, at quality medium, answers 22 s and 49 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 its render has started, 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 480p, 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. Every take is a creation, kept while your account is covered, by default (retention and expires_at say how long): 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

Short questions whose answers change the words the most (names, places, memories, the tone), five a round, each with three choices and a free field, asked one round at a time: round 1 from the request (its first question asks the style when the request names none), round 2 from the answers to round 1, round 3 from all ten, each round more personal than the last. 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", "round": 1}'
# {"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": "…ask round 2 with every answer so far as 'answers' and every question shown as 'asked'…"}

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", "round": 2,
     "answers": [{"question": "Quel style pour Léa ?", "answer": "Folk acoustique"}, {"question": "Son pays préféré ?", "answer": "Le Portugal"}],
     "asked": ["Quel style pour Léa ?", "Son pays préféré ?", "Qui chante ?"]}'
# {"id": "r2q1", "round": 2, "text": "Un souvenir de Léa au Portugal ?", "choices": ["Le tram de Lisbonne", "Un fado le soir", "La plage de Nazaré"], "free_text": true, "placeholder": "Les pastéis de nata"}

asked lists every question shown, answered or skipped: none comes back. Without stream, the round at once; without round, the first round. Add a "skip" yourself and pass what was answered to /write as answers. A light cloud call per round, 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), 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, fillAccepted and ignored since 2026-09-30: one queue and one price for everyone. Leave it out.
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 puts one in the bin (48 h to restore it). An element is kept like any creation, while your account is covered by default.

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: while your account is covered by default, 31 days or 48 h on request; 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 and the cutout run on our own machine, for every account and free of the week. An edit in the default mode runs on our own engine too, counted in GPU seconds, like the enlargement; the edit in the medium mode, the generation and the extension are cloud calls, counted on the week at what they cost. Every launch needs 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",
#    "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, medium, highThe mode. low (default) spends little: our own machine edits your picture (GPU seconds), a partner under contract makes one from words. medium: the partner for both, about 1.2 times the week of low for an edit. high: the partner at its finer setting for both, about 1.6 times low for an edit and twice for a picture from words. auto and draft read as low, keep as high.
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 step at a partner under contract). Send false to give the engine your words as they are.
lanepriority, normal, fillAccepted and ignored since 2026-09-30: one queue and one price for everyone. Leave it out.
confidentialitylow, medium, highThe account's default when absent; high keeps the work on our own machines.

Three modes (quality). low, the default, the one that spends little: a picture you edit is painted on our own machine (counted in GPU seconds), a picture made from words at a partner under contract. medium: the partner for both. high: the partner at its finer setting for both. An edit with references goes to the partner whatever the mode: our machine does not take a second picture. When one cannot answer, another takes over, and a picture that holds a face (faces, from POST /v1/image/faces) or comes with a reference takes the path that keeps people as they are. The director writes the instruction from your picture and your words by default. POST /v1/image/estimate with the same body tells the seconds and what the week is asked, without launching anything (week: share_pct of the account's week for one such edit, left_pct of the week left, about_per_week and about_left, how many such edits a whole week and what is left hold; never money); GET /v1/image/me says whether our machine is up. (auto and draft read as low, keep as high.)

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. POST /v1/image/generate/estimate with the same body answers like /estimate, without launching anything: weigh a series before you start it. Each reference is sent to the engine and weighs about three quarters of a medium picture of its own, whatever the mode.

Four proposals in one call

curl -X POST https://api.infinitepeople.ai/v1/image/sheet \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"request": "the four friends dance a folk dance at a beer festival, golden evening light", "references": ["$PHOTO"]}'

The body of /generate, for a video's start picture chosen among four: one picture, a 2×2 sheet of the scene, cut into four pictures of the account in result.cases (top left, top right, bottom left, bottom right, each with its url, width and height); result.url is the whole sheet. Always painted at the high quality (the cloud engine's medium tier), whatever quality says: the faces of the references stay recognisable in every panel. shape: landscape (1088×608, four panels of 544×304, default), portrait (608×1088), square (848×848) or a WIDTHxHEIGHT. Start the video from the chosen panel's url. A refusal of the engine stands: a sheet is never painted elsewhere. POST /v1/image/sheet/estimate weighs it first.

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, 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: none (default) is the frame as the engine painted it, your picture redrawn with the new bands, one coherent picture without a seam; smart keeps your picture as it is as far as possible — the engine's pixels where it clearly changed things, connected to the new bands, yours everywhere else (a seam can show where it placed things otherwise, a cord or a basket at the edge); selection pastes your picture back whole, pixel for pixel. result.kept is the share of your picture that came back untouched (0 with none, 1.0 = all of it), 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. To change the picture and widen its frame at once, one edit without a selection and a shape does both in one pass.

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 with a week: 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 · Infinicia and its job's id 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/me → limits.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 images_access_deniedthe account is not let into Infinicia (its door closed by the admins; any account comes in on its own at its sign-in to Infinicia); retrying does not change it, [email protected] does
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 from a sentence: slides (or live slides, the room taking part on its phones), a one-page site, a live quiz, or a poster to print. The page is a single HTML file on our kit (themes, layouts, components, fonts), served by its id, sandboxed; it stays as long as your account's retention says. Two qualities: low (the default), the lightest, and medium, with more depth; both written at partners under contract, counted on the week, never trained on your content. At the high confidentiality, low is written on our own machines (counted in GPU seconds) and medium is not offered. 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, live, site, quiz, posterSlides to present; live, slides the room joins by QR code and answers on its phones between the slides that tell; a one-page site to send; a quiz to play live (questions, answers, points for speed, a ranking); a poster, one printed sheet (A4 or A3).
themestudio, studio-dark, magazine, atelier, neon, night, paper, ocean, cinema, kids, forest, warm, mono, candy, slateLeft out, our model picks one for the subject (studio when nothing says otherwise). The kit's palette: GET /v1/deck/kit.
templatean id of GET /v1/deck/kit → templatesA 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", "use"}Files of ours (a project's picture, a video, a song); title is what our model reads about each. use: place (the default), our model puts it in the page; style, a picture the page takes its colours and its feel from, looked at and never placed (one at most); read, a picture, a comic's page, a video or a recording the page is written from: what it shows and says becomes material, it is not placed (8 at most).
languagefr, en…The page's language; the prompt's when left out.
qualitylow, mediumThe lightest model (the default), or a larger one, each counted at its cost; medium not at the high confidentiality level. rapide and soignee, their names of before, are still read.
lane, confidentialityas everywhereAt high, the page is written on our own machine only (low).

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/me → limits.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 machines are busy and no partner is allowed at this confidentiality level; 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 machines: saying at 48 kHz, converting at 24 kHz. Every plan, counted on the week in GPU seconds; what a creation makes is kept as long as your account's retention says (while your account is covered, 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 or your account's retention ends. Beside your own voices, ready-made stock voices speak twelve languages in the medium and high qualities (below). Shared (POST /v1/creations/{creation_id}/shares), a kept voice is lent for its use: view uses it (says texts, converts recordings into it, speaks in a video, from the app or an agent), edit also modifies it (its name, its register); deleting it stays yours. 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, one is made on our own machine (a couple of seconds of GPU on the week; at every confidentiality level, the free week included, not once it is behind without a plan) — when our machine cannot, a partner under contract steps in where the level allows it (counted at its cost); otherwise the voice is cloned from its timbre alone (faithful: false), still the same voice, a little freer. language (fr, en…) helps the transcript, most of all for a short recording with an accent; gender (female | male) says the voice's register.

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": "a woman in her fifties, deep and slow", "language": "fr", "takes": 3} (the description reads as well in French; language is the sample's, one of fr, en, de, es, it, pt, nl, pl, ru, ja, ko, zh) answers one take per draw (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: each one a new draw). An accent comes from the language a voice is designed in, not from its description: a voice designed in Italian keeps its Italian accent when it says French — design it with language it, keep it, then say your French texts with it, in the standard quality (medium and high give every language its native accent). 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."}'
# → {"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, In the standard quality each take is drawn anew: our engine says takes asked together in one batch, which makes it fast and cheap, and it takes no seed. A long text is fine (6 000 characters at most, about a minute of speech for 900): it is said in groups of sentences, all at once, and put end to end; a minute of speech is ready in a few seconds. standard is counted at a fixed rate whatever the crowd on our cards: half a second of card a take plus a tenth of a second per second of speech (GET /v1/voice/me → week_pct_per_1000.standard says what a thousand characters take of your week). quality chooses who says it: standard, our own machines (the default, the voice's own delivery); medium, a partner under contract (about 3 to 6 times the week standard takes, by plan); high, the same partner at its finest (about 6 to 12 times). Those two are more expressive and play a mood: joy, sadness, calm, whisper, enthusiasm, tenderness, anger. In them the text goes to that partner, which keeps neither it nor trains on it, so never at the high confidentiality level; the voice is cloned there at its first take in medium or high, and kept while it serves. GET /v1/voice/me says the qualities your account has (qualities), the moods, and the share of the week a thousand characters take in each (week_pct_per_1000).

Stock voices. GET /v1/voice/voices?stock=true adds 32 ready-made voices after yours (&language=fr for one language's): two per country in twelve languages — fr-FR, fr-CA, en-US, en-GB, es-ES, es-MX, de-DE, it-IT, pt-BR, pt-PT, nl-NL, pl-PL, ru-RU, ja-JP, ko-KR and cmn-CN (Mandarin, language zh) — a woman's for promos (gender female) and a man's narrator (male). Each reads {"id": "fr-FR-koraly", "name": "Koraly", "language": "fr", "country": "fr-FR", "gender": "female", "about": "bright and energetic, for promos", "sample_url": "…/voice/stock/fr-FR-koraly.mp3", "stock": true, "qualities": ["medium", "high"]}. Say a text with one as with a kept voice ({"voice": "fr-FR-koraly", "text": …}), in the quality its qualities lists, default_quality when absent. Most speak in medium (their default) or high, with the same moods, never in standard. The 24 voices of our characters — a woman and a man per language: fr-FR, en-US, es-MX, de-DE, it-IT, pt-BR, nl-NL, pl-PL, ru-RU, ja-JP, ko-KR, cmn-CN — also have a standard of their own, their default: our engine on a voice we designed for each, a sibling of its medium and high one (qualities: ["standard", "medium", "high"]); a recording can be converted into one of them, never into the others. It cannot be renamed, deleted or shared; what you make with it is yours like any creation. For a nationality, take the voice of that country (country); for a foreign accent, design a voice in the accent's language (below). They are said at a partner under contract, which keeps neither the text nor the result; they weigh about half of a kept voice's medium and high on the week (GET /v1/voice/me → stock: week_pct_per_1000, wait_s_base, wait_s_per_1000; null when they are not offered to the account). Never at the high confidentiality level.

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.

4. A dialogue

curl -X POST https://api.infinitepeople.ai/v1/voice/dialogue \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"turns": [{"voice": "fr-FR-nicolas", "text": "Bonjour Margaux, prête pour l'émission ?"},
                 {"voice": "fr-FR-koraly", "text": "Toujours ! On commence par quoi ?"},
                 {"voice": "61e7…", "text": "Par moi, évidemment."}]}'
# → a creation; when ready, result.url is the dialogue (WAV, 48 kHz) and result.turns says who speaks when

Up to three voices taking turns in one recording: kept ones, stock ones, or both. Each turn is said by its voice in the quality asked — one for the whole dialogue, standard when every voice has it, medium otherwise — then the turns are put end to end at one loudness, a short silence between two. result.turns gives each turn's voice, text, start_s and end_s, for subtitles or a player. 6 000 characters at most for all the turns together; a mood per turn in medium and high. Counted as the sum of its turns, like as many takes; a turn that cannot be said fails the whole dialogue and the week is given back. A dialogue in someone else's shared voice comes back to that voice's owner, as a take does.

A podcast in slices: a dialogue can follow the one before — follows with that dialogue's id (yours), and format mp3 (about 2 MB for three minutes, where the WAV takes 17). The slice opens on the dialogue's pause, so slices played end to end sound as one; in medium and high its voices know how the last one ended. result.series (the first slice's id), result.index (0, 1, 2…) and result.series_s (the series' length so far, this slice included) place it; a slice follows one already said (409 not_ready before). Ask for the next slice while the listener is in this one — about two minutes into three — never ahead of the listening.

5. A document read aloud

curl -X POST https://api.infinitepeople.ai/v1/voice/reading \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"title": "Le voyage",
       "turns": [{"voice": "fr-FR-nicolas", "text": "Chapitre 1 : le départ.", "part": "Chapitre 1"},
                 {"voice": "fr-FR-nicolas", "text": "Il partit à l'aube, sans un mot."},
                 {"voice": "fr-FR-koraly", "text": "Attends-moi !"}]}'
# → a creation; when ready, result.url is the reading (MP3) and result.parts its chapters, each with its own url

A text read from start to end, up to an hour: one voice over it all, or the narrator and the characters' voices, six at most. Send it a paragraph a turn; a turn carrying part opens a part (a chapter) — the title is said if it is in its text. 60 000 characters at most; under that, the week decides. POST /v1/voice/reading/estimate with the same body answers first what it would take — characters, speech_s, estimate_seconds, week_pct — and, in over, the limits it passes, in words; nothing is made, nothing counted. While it is read, progress says the turns said (done of of) and the parts ready, each heard at its own url before the rest is done. Once ready, result.url is the whole as an MP3, result.parts gives each part's title, start_s, end_s and url, and result.turns who reads what when (start_s, end_s, part). Counted like a dialogue.

fieldvalues
voicean id of GET /v1/voice/voicesThe voice that says (a kept one or a stock one), or takes over (a kept one).
texttext, 1 to 6000 charsWhat is said (say only).
qualitystandard, medium, highWho says it: our machines (default), or a partner under contract, with moods; medium about 3 to 6 times the week of standard, high 6 to 12 (say only); a stock voice: medium (default) or high.
moodjoy, sadness, calm, whisper, enthusiasm, tenderness, angerThe delivery, with quality medium or high (say only).
seedintegerMedium and high: the same seed says the same text nearly the same way. A standard take and a designed voice are drawn anew each time.
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, fillAccepted and ignored since 2026-09-30: one queue and one price for everyone. Leave it out.
confidentialitylow, medium, highSaying, designing, converting and a recording's transcript run on our own machine: available at every level (at high the transcript never falls back to the cloud: the voice is then kept from its timbre). At high the moderation of a text is out, so a text cannot be said yet; keeping a recorded voice and converting work. The medium and high qualities and the stock voices are out at high.
retentionsubscription, month, ephemeralHow long this creation stays (while your account is covered, and 13 months after, opened or not; 31 days; 48 hours then gone for good); 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 its work had already started: 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/me → limits.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
422 quality_not_offeredmedium and high are not open to this account, or standard asked with a stock voice
422 voice_not_offeredthe stock voices are not open yet; say it with a voice of yours
422 mood_not_offereda mood without quality medium or high
422 not_available_at_confidentialitymedium, high or a stock voice asked at the high level (they are said outside our machines)
503 no_roomno room at our partner for one more voice right now (each is kept a month after its last use); standard works, and so does a voice already used in medium or high
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_unavailableour machines are 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

Classify a text

Questions asked of a text or a JSON state, answered with calibrated probabilities: yes or no, a choice among options, a score on a scale. Made for pipelines: sort a support ticket, route a document, score a lead, check a rule. It runs on our own machine only (in France): nothing you send leaves it, and nothing of it is kept, neither the state nor the questions nor the answers; only a technical trace (the account, how many questions, the tokens, the time). With the Standard and Pro plans, licensed to the person: your account, your keys. Counted on the week like every trade, by the tokens it reads and writes, turned into the seconds of our GPU they take (a few hundred tokens read are a tenth of a second).

curl -X POST https://api.infinitepeople.ai/v1/classify \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"state": "My September invoice is charged twice and nobody has answered for ten days.",
       "questions": {
         "complaint": {"type": "noul", "instructions": "Is the message a complaint?"},
         "topic": {"type": "choice", "instructions": "Main topic",
                   "criteria": {"delivery": null, "billing": "invoices and payments", "product": null}},
         "urgency": {"type": "score", "instructions": "How urgent is it?", "criteria": ["low", "medium", "high"]}}}'
# → an answer like {"model": "infinicia-systemone", "mode": "fast",
#    "answers": {"complaint": {"type": "noul", "noul": 0.97},
#                "topic": {"type": "choice", "choice": "billing", "confidence": 0.97, "probabilities": {"delivery": 0.01, "billing": 0.98, "product": 0.01}},
#                "urgency": {"type": "score", "score": 1.1, "legend": {"0": "low", "1": "medium", "2": "high"}, "probabilities": {…}, "confidence": …}},
#    "usage": {"input_tokens": 375, "output_tokens": 3}, "latency_ms": 390, "request_id": "…"}

The answer comes back at once (no job to poll). Each question is answered on its own, in the same call.

field
stateA text, or any JSON value: what is judged. About 90 000 characters at most (some 26 000 tokens).
questionsUp to 16, each name: {type, instructions, criteria?}; instructions says what to decide, in any language.
type: noulYes or no. criteria optional (true and false, each described). Answers noul, the probability of yes, 0 to 1.
type: choiceOne option among criteria (option: description or null, up to 255). Answers choice, its confidence and every option's probabilities.
type: scoreA level on a scale: criteria lists the levels in order (up to 255). Answers score, the expected level as a decimal (1.1: just above the second), its legend, probabilities and confidence.
modefast (the default): about 0.2 s, calibrated probabilities, so a threshold holds (decide alone above a confidence, send the rest to a person). precise: 2 to 3 s, a little more accurate (on a public benchmark of 656 questions: 82 % right in fast, 87.5 % in precise), but its probabilities sit near 0 or 1: no threshold to set there. A model whose name contains precise asks the same.

The confidences follow the reference formulas of TypeSafe's System One, and the contract is the same: a client of theirs moves by pointing its base URL at https://api.infinitepeople.ai/v1 (their SDK included); POST /v1/systemone answers exactly as /v1/classify, with your key.

code
402 not_on_planthe classifier comes with the Standard and Pro plans
402 week_overthe week is spent; resets_at says when it starts again
413 too_largethe state is over about 26 000 tokens: send a part
422 invalid_requesta field is wrong (a type, too many questions or options); nothing is counted
429 too_many_requestsfour classifications of the account already running; retry_after_s is one second
503 classify_unavailableour machine does not answer just now; retry (retry_after_s)

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
effectid a video clip (adds one) or an effect (changes it); effect its name; begin, end seconds on the clip's own clock (0 = its first frame); paramsone effect of the catalogue below; remove takes an effect's id too

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. A picture (picture:5, an edited one image:4) never goes on a track, it starts a shot (make, below); a presentation (deck:) and a capture (capture:) are not media: the bench says so (422).

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.

Effects are a named catalogue, never free filters: flash (intensity 0.1..1, color white / black), fade_in and fade_out (color black / white), zoom (direction in / out, amount 0.05..0.5, x, y the point 0..1), shake (amplitude 0.002..0.05 of the width, frequency 1..20 a second), bw (amount), tint (tone sepia / warm / cool, amount), blur, vignette (amount 0..1), mirror (axis horizontal / vertical). An effect lives on its clip and moves with it; left out, its window is its own (a fade out: the last second, a flash: 0.3 s), and one bound alone keeps the rest (a fade in until 2 s starts at 0). Zoom, shake, mirror, colour, blur and vignette act on the clip's picture, inside its bars; a flash and the fades on the whole screen. A look the catalogue does not name is several effects on the same window; the director composes them from a sentence (“make it look like an old film”). 8 effects at most on a clip; a parameter out of its bounds is a 422 that says them.

Look before you change, and after: POST /v1/montages/{id}/filmstrip answers one picture of the montage, a frame of every clip in reading order (url; shots says which clip each frame is and when it plays), and POST /v1/montages/{id}/preview the montage at 480p without sound (url). Both take a few seconds of our own machines on the week and live 7 days; nothing keeps them. /direct looks at the filmstrip before it answers (looked_at; "look": false to skip it).

A clip made again (another take, or the same shot at 768p or 1080p) carries newer in summary.video: job_id, kind (retake, or upscale for the same take sharper), definition (768p) and from, the handle to send; a take less defined than the clip is never offered. {"op": "replace", "id": "<clip>", "from": "<newer.from>"} puts it in the clip's place with its bounds and transition. Nothing is replaced for you: ask the person.

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 its render
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 its render has started) 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, or high: our machines only, with the Standard and Pro plans) cannot do a step of the request; refused names the option to change
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/me — user (your name, your picture's URL), plan (Gratuit, Essentiel, Starter, Standard, Pro), week, limits and services per trade, retention (what your next creations get: subscription, while your account is covered and 13 months after; month, 31 days; ephemeral, 48 hours — keep_until once the account is not covered, notice when creations go within 30 days). 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 Powered by MiniMax H3. Infinicia orchestrates the best models for each step, on our own machines and at partners under contracts that forbid training on your content. 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.