Skip to main content
A character is built from files you upload first. Each file takes two requests: POST /v1/uploads returns a short-lived upload link, and a PUT sends the bytes to it. The upload_key from the first answer is what you pass in references when you create a character. POST /v1/uploads · scope chars:write
r2Key holds the same value as upload_key and is deprecated; read upload_key. thumb is a second link the dashboard uses for previews; it is null unless you send thumbBytes, which you don’t need.

2. Send the bytes

PUT the file to uploadUrl with exactly the headers from headers, before expiresAt (10 minutes):
The link is signed for one key, one content type and one size. A different content-type, a file of another size, or a late request is refused, and you ask for a new link. No API key goes on the PUT: the link carries its own permission, so treat it as a secret until it expires.

3. Use the upload key

Pass each upload_key in references, as a string or as { "upload_key": "..." }:
Finish every PUT before you create the character. The API does not check that the bytes arrived: in production a reference that was never sent fails the build, and you get char.failed; sandbox does not notice at all.

What you can upload

A character takes at most 20 files across face, body and voice, so 12 face and 12 body photos together are refused. About 30 seconds of clear speech is enough for a voice. For which photos make a good character, see Finetuning.

Who can use an upload

An API key acts as the person who created it, and an upload belongs to that person. Uploads made with one key can be used by any key that same person created, in either environment. A key created by a teammate cannot use them, and the character request answers 422 naming the reference, for example references.face[0] is not one of your uploads. Rotating a key from Settings makes the new key act as whoever rotated it. If a teammate rotates your key, upload the files again with the new key.

Storage

Uploads are stored files, with sandbox keys too, and count toward your team’s storage allowance. When an upload would go over it, POST /v1/uploads answers 409 with the code storage_limit_reached.

Upload a folder of photos

This uploads every .jpg in a folder and prints the keys as a JSON array, ready for references.face. It needs jq.
The same in Node 18 or later:

Errors

See Errors for the shape of an error body.