# Ready programs

GPU Forge packages, runs and updates a set of open AI models as **ready programs**. There is nothing to install and no model to download. You can use them in two ways:

- **As jobs** (`POST /v1/jobs`): one request, one result, pay per render second. See [Jobs](jobs.md).
- **On your own machine**: add the program to your account, start a machine and call the program's own API, paying for the machine per second.

| Program | What it does | Job routes |
|---|---|---|
| **FLUX 3 Klein** | Makes images from a prompt, edits images, combines up to 4 reference images | `/v1/generate`, `/v1/edit` |
| **Krea 2** | Makes images from a prompt (with an optional prompt writer), edits images | `/v1/generate`, `/v1/edit` |
| **LTX-2.5** | Video with its own soundtrack, from a prompt or from a first frame; lips can follow speech you supply | `/v1/jobs/text-to-video`, `/v1/jobs/image-to-video` |
| **MiniMax H3** | Video with sound from a prompt or frames; a talking person from one photo and your words; draft and full quality | `/v1/jobs/text-to-video`, `/v1/jobs/image-to-video`, `/v1/jobs/talking-video` |

The catalog is the source of truth for which routes are sold as jobs: always read `job_routes` from `GET /v1/catalog`.

## Rules for every image program

- **Every image carries an invisible GPU Forge mark**, hidden in the pixels and signed in the file.
- **Images of children are never made.** A request that asks for one is refused (`422`).
- Results are generated by AI and may be inaccurate or unexpected; check them before you rely on them.
- Use is subject to the [Terms of Service](https://gpuforge.io/terms) (acceptable use).

## The catalog

```
GET /v1/catalog
```

```bash
curl -s "https://api.gpuforge.io/v1/catalog" -H "Authorization: Bearer $GF_KEY"
```

Returns `{"programs": [...]}`. Each program:

| Field | Description |
|---|---|
| `capability_id` | The **program id**. Use it as `program` in a job, and in `POST /v1/catalog/{program_id}/add` |
| `name` | Short name |
| `title` | Display title |
| `summary` | What it does |
| `version` | The current version. New versions reach your copy by themselves |
| `min_vram_gb` | GPU memory the program needs per card |
| `max_price_per_hour` | The price limit a new copy of the program starts with (you can change it on your copy) |
| `avg_price_per_hour` | What one hour of a machine for this program usually costs you now: the middle of the cheapest machines that fit. Jobs are priced from this. `null` when nothing fits right now |
| `job_routes` | The routes you can run as jobs. Empty = machine use only |
| `parked_machines` | Parked machines kept for you on a new copy (always `0`; parking is your choice) |
| `added` | The id of **your copy** of this program, or `null` if you have not added it |
| `how_to_use` | A one-line reminder of the machine flow |

## Using a program on your own machine

1. **Add it to your account** (once):

   ```bash
   curl -s -X POST "https://api.gpuforge.io/v1/catalog/<program id>/add" \
     -H "Authorization: Bearer $GF_KEY" -H "Content-Type: application/json" -d '{}'
   ```

   `201` returns your copy's `capability_id` and a **program key** (`gf_api_key`, shown once). Adding the same program twice answers `409` with the id of the copy you already have.

2. **Start a machine**: `POST /v1/capabilities/{your copy id}/deploy` (or `/scale` with `{"desired_pods": 1}`). A parked machine of the program is woken first if one is free. See [Machines](machines.md).
3. **Wait** for `capability_state: ready` on `GET /v1/fleet/{pod_id}`. `capability_url` is the machine's address.
4. **Read the program's guide**: `GET <capability_url>/v1` with `Authorization: Bearer <program key>`. Every request to the program needs that header (`401` without it).
5. **Stop it**: `POST /v1/fleet/{pod_id}/release` or `/park`.

What you can change on your copy: its settings (`idle_ttl_seconds`, `max_price_per_hour`, `surge_price_per_hour`, `daily_budget_usd`, `interruptible`, `only_gpus`, `excluded_gpus`, `min_gpu_family`, `idle_by`, `price_reduction`), `active`, `dormant_pods`, and the program key (`secrets.env.GF_API_KEY`). The program itself (its files, commands and settings inside) stays ours and is updated by us: reading your copy shows those parts empty, and a machine of a copy cannot be started with your own start command, image, ports or environment. There is no SSH into machines of ready programs.

Good to know:

- **Nothing is kept between starts.** Each start or wake begins with an empty working folder. Upload your inputs again after every start or wake, and download each result as soon as it is made.
- For a minute or so after `ready`, a video program may report `"machine": "warming"` on `GET /v1/status`; the first request waits a little.
- For machines running our ready programs, the program's output is kept for 30 days: `GET /v1/fleet/{pod_id}/output?since=&limit=` (1–2000, default 200).

---

## FLUX 3 Klein (images)

Makes images from a prompt, edits images, and combines up to 4 reference images. The prompt goes to the image model **exactly as written**; it works best in English, as one full description: the medium ("Full-color photograph, ..."), the scene, light and camera, then each subject with concrete colors and materials.

### `POST /v1/generate`: an image from a prompt

| Field | Required | Description |
|---|---|---|
| `prompt` | yes | What the image shows, up to 6000 characters. Words that must appear in the image go in double quotes. With references, say what comes from each by order ("the jacket from the second image") |
| `reference_images` | no | 0–4 images to combine, each base64 (JPEG, PNG or WebP, up to 20 MB) |
| `width`, `height` | no | Both or neither. 256–2048, rounded to multiples of 16, at most 4.2 megapixels. Default 832 × 1216 |
| `seed` | no | Integer |

### `POST /v1/edit`: change an image

| Field | Required | Description |
|---|---|---|
| `image` | yes | The image to edit, base64 |
| `prompt` | yes | One instruction that states the end result and ends with what stays the same, e.g. "Change the sky to a sunset with warm orange light. Keep the buildings and the street unchanged." |
| `reference_images` | no | 0–3 more images to take a person, look or object from (the image to edit counts as the first image) |
| `width`, `height` | no | Default: the input image's shape at about 1 megapixel |
| `seed` | no | Integer |

### Response (both routes, `200`)

```json
{"image": "<base64 JPEG>", "mime_type": "image/jpeg", "width": 832, "height": 1216, "seed": 7, "seconds": {"total": 3.1}}
```

`seconds.total` is the render time; jobs are billed on it. Other fields may be present; ignore fields you do not use.

### Errors

`400` invalid input (the message names the field) · `401` missing or wrong program key · `422` refused (the message says why) · `503` busy (see `Retry-After`) · `500` the render failed.

On your own machine, one image renders at a time and up to 8 wait in line. A warm machine makes an image in a few seconds.

---

## Krea 2 (images)

Makes images from a prompt, and edits images. By default a **prompt writer** first turns your prompt into a full description; turn it off to send your prompt to the image model exactly as written.

### `POST /v1/generate`: an image from a prompt

| Field | Required | Description |
|---|---|---|
| `prompt` | yes | What the image shows, in any language, up to 6000 characters. Words that must appear in the image go in double quotes |
| `reference_images` | no | 0–2 base64 images (JPEG, PNG or WebP, up to 20 MB). One image: the person or style to keep (an image with a face is cropped to the head first, so only the face and hair carry over). Two images: two references. None: plain text-to-image |
| `width`, `height` | no | Both or neither. 512–2048, rounded to multiples of 16, at most 2.4 megapixels. Default 832 × 1216 |
| `seed` | no | Integer |
| `characters` | no | Up to 4 people by name: `{"name", "gender" (female, male or transgender), "looks", "clothes", "reference" (1 or 2: which reference image shows them)}`. Then use their names in the prompt |
| `camera` | no | `auto` (default), `fixed`, `selfie` or `mirror` |
| `style` | no | `photo` (default), `anime` or `stylized_3d` |
| `place`, `time_of_day` | no | Short text |
| `enhance_prompt` | no | `true` (default): the prompt writer expands your prompt first. `false`: the prompt goes to the image model as written (best in English, as a full description; for an edit, describe the result). With `false`, `characters`, `camera`, `style`, `place`, `time_of_day`, `image_description` and `image_camera` are refused (`400`): write them into the prompt |

### `POST /v1/edit`: change an image

| Field | Required | Description |
|---|---|---|
| `image` | yes | The image to edit, base64 |
| `prompt` | yes | The change, e.g. "make it sunset" or "she stands up and stretches" |
| `reference_image` | no | A second image to take a person, look or object from |
| `width`, `height` | no | Default: the input image's shape at about 1 megapixel |
| `image_description` | no | What the image to edit shows; helps keep the rest of it the same |
| `image_camera` | no | How the image to edit was taken: `fixed`, `selfie` or `mirror` |
| `seed`, `enhance_prompt`, `characters`, `camera`, `style` | no | As above |

### Response (both routes, `200`)

```json
{"image": "<base64 JPEG>", "mime_type": "image/jpeg", "width": 832, "height": 1216, "seed": 7, "seconds": {"total": 16.1}}
```

Other fields may be present; ignore fields you do not use.

### Errors

`400` invalid input (names the field) · `401` program key · `422` refused (the message says why, for example a prompt that cannot be drawn) · `503` busy (`Retry-After`) · `500` the render failed. One image renders at a time; up to 8 wait in line.

### Example (on your own machine)

```bash
curl -s "<capability_url>/v1/generate" \
  -H "Authorization: Bearer $PROGRAM_KEY" -H "Content-Type: application/json" \
  -d '{"prompt": "A cyclist rides along a coastal road at golden hour, the sea on the left.", "seed": 7}' \
  | python3 -c "import sys,json,base64; open('out.jpg','wb').write(base64.b64decode(json.load(sys.stdin)['image']))"
```

---

## LTX-2.5 (video with sound)

Makes 24 fps video with its own soundtrack, from a prompt alone or opening on a picture you give. If you send finished speech (`audio`), the lips follow it. Your prompt goes to the model exactly as written.

### Job routes

| Route | What it makes |
|---|---|
| `/v1/jobs/text-to-video` | A clip from the prompt alone |
| `/v1/jobs/image-to-video` | A clip that opens on `first_frame` |

Both wait for the clip and answer with the finished video (within 580 seconds). In a job, send pictures and sounds **inline**, as a `data:` URI or plain base64.

### Request fields

| Field | Default | Description |
|---|---|---|
| `prompt` | required | What happens, as the model reads it (up to 8000 characters) |
| `first_frame` | required for image-to-video | The picture the video opens on, cut to `format` at `crop` |
| `audio` | none | Finished speech (from any text-to-speech or a recording), 0.3–11.2 s. The clip gets 0.5 s of quiet, then this speech; the lips follow it, and the clip is exactly that long (at least 3 s) |
| `seconds` | `5` | 3–12. Only without `audio`; the model then makes the whole soundtrack from the prompt |
| `format` | the picture's shape, else `9:16` | `9:16`, `16:9`, `1:1`, `3:4`, `4:3`, `2:3`, `3:2`, `21:9` (about 0.5 megapixels, e.g. 544 × 960) |
| `crop` | `{"x": 0.5, "y": 0.3}` | Where the frame sits on a picture of another shape (0–1 each way; 0.3 keeps faces in) |
| `seed` | random | The same seed and request give the same video |
| `upload_url` | none | Your own presigned **PUT** URL (S3, R2 or any S3-compatible store). The finished MP4 is also uploaded there |

### Response (`200`)

```json
{"video": "<base64 MP4>", "mime_type": "video/mp4", "seed": 81213, "format": "9:16", "clip_seconds": 5.0, "render_seconds": 14.2}
```

With `upload_url`, the answer also has `upload`: `{"status": "done", "http_status": 200, "bytes": ...}` or `{"status": "failed", "http_status": ..., "error": "..."}`. Any error is a non-2xx answer `{"error": "..."}`.

### Writing the prompt

- **Describe the change, not the picture.** With `first_frame`, the first frame already shows who and where; write what happens next: the motion, the expression, the camera.
- **Speech:** say the person talks in the first words ("She looks into the camera and talks warmly, ..."). The lips follow your `audio`.
- **Short and concrete:** one or two actions per clip, in the order they happen; the camera in its own sentence ("The camera holds still." / "The camera slowly pushes in.").
- **Sound without `audio`:** write it into the prompt ("Rain on the window, a quiet café in the background.").
- Head-and-shoulders framing gives faces the most detail.

### On your own machine

The machine also offers a queue-based API: `POST /v1/text-to-video` and `/v1/image-to-video` answer `201` with a clip id; `GET /v1/clips/{id}?wait=N` (up to 600 s) returns the clip with a download `url`; `POST /v1/clips/{id}/cancel`; `GET /v1/clips`; `POST /v1/uploads/photo` and `/v1/uploads/audio` (multipart) return a name you can use in `first_frame` and `audio`; `GET /v1/status`. On your own machine, pictures and sounds may also be an `http(s)://` URL the machine can fetch. Full details: `GET <capability_url>/v1`.

---

## MiniMax H3 (video with sound)

Makes 24 fps video with its own soundtrack. It can open on a picture, end on a picture, follow reference pictures and voice samples, and make a person in a photo say your words in a chosen voice. Every clip has two qualities: a fast **draft** for checking, and **hq** for delivery.

### Job routes

| Route | What it makes |
|---|---|
| `/v1/jobs/talking-video` | The person in a photo says your words to the camera |
| `/v1/jobs/text-to-video` | A clip from the prompt alone |
| `/v1/jobs/image-to-video` | A clip that opens on `first_frame` and/or ends on `last_frame` |

Each job stands alone and answers with the finished clip (within 580 seconds). In a job, send pictures and sounds **inline** (`data:` URI or plain base64) and voices by their ready id. For full quality send `"quality": "hq"`: one call makes the draft pass and its upscale.

### Quality

| | `draft` (default) | `hq` |
|---|---|---|
| What | One fast pass at half size (352 × 608 for 9:16) | The same pass, upscaled with a detail pass to full size (704 × 1216) |
| For | Checking motion, framing, timing, speech and sound | The video you deliver |
| Looks | Soft; faces and eyes are smeared at this size | Sharp faces and eyes |

The same `seed` and request give the same motion, so a draft you like can be made again as `hq` with its `seed`.

### `/v1/jobs/talking-video` fields

| Field | Default | Description |
|---|---|---|
| `photo` | required | The person who talks; the video opens on it |
| `words` | required (or `audio`) | What they say: up to about 13 s of speech (some 30 words). The clip's length follows the words (5–15 s) |
| `voice` | chosen by the model | A ready voice id, for example `emma`, `anna` or `mike` |
| `language` | `en` | The language of `words`: `en es fr de it pt ru ja ko zh he ar nl pl tr` (or the English name) |
| `audio` | none | Instead of `words` and `voice`: a finished recording; the lips follow it |
| `seconds`, `format`, `quality`, `detail_steps`, `seed`, `crop`, `upload_url` | | As below |


The answer also has `prompt`: the prompt the program wrote for you. Send an edited version to `/v1/jobs/image-to-video` for full control.

### `/v1/jobs/text-to-video` and `/v1/jobs/image-to-video` fields

| Field | Default | Description |
|---|---|---|
| `prompt` | required | The H3 prompt, sent to the model as is (see "Writing the prompt") |
| `seconds` | `5` (with `audio`: its length) | Whole seconds, 1–15. With `audio`, at least the audio's length |
| `format` | the frame's shape, else `9:16` | `9:16`, `16:9`, `1:1`, `3:4`, `4:3`, `2:3`, `3:2`, `21:9` |
| `quality` | `draft` | `draft` or `hq` |
| `detail_steps` | `1` | hq detail-pass steps, 1–3 |
| `seed` | random | The same seed and request give the same motion |
| `first_frame` | none | The video opens on exactly this picture (cut to `format` at `crop`). It is also `<Picture 1>`, the look reference, unless `first_frame_is_reference: false` |
| `crop` | `{"x": 0.5, "y": 0.3}` | Where the frame sits on a picture of another shape |
| `last_frame` | none | The video ends on this picture |
| `audio` | none | Finished speech or sound, up to 15 s. The clip's soundtrack is this recording and the picture lip-syncs to it. Write the speaking in plain words ("she talks to the camera") |
| `voice` | none | A ready voice id: `<Audio 1>`, the voice for speech your prompt writes in `<d>` lines |
| `references` | none | `{"images": [up to 9], "audios": [up to 3]}`. Images are `<Picture 2>…` after a first frame (else from `<Picture 1>`). Audios are voice samples (about 10 s of one clean voice) |
| `upload_url` | none | Your own presigned **PUT** URL; the finished MP4 is also uploaded there |

### Response (`200`)

```json
{"video": "<base64 MP4>", "mime_type": "video/mp4", "seed": 8121, "quality": "draft", "format": "9:16", "seconds": 5, "render_seconds": 18.4}
```

Plus `upload` when you sent `upload_url`, and `prompt` on talking-video. Any error is a non-2xx answer `{"error": "..."}`.

### Writing the prompt

An H3 prompt has six sections: `subject_definitions`, `summary`, `retention_analysis`, `detailed_description`, `overall_soundscape`, `non_diegetic_music`. Refer to inputs with exactly the labels you sent: `<Picture n>`, `<Audio n>`, and one `<Subject n>` per person. Name the task type in the summary: first or last frame = `[keyframe completion]`, references = `reference generation`, a voice sample = `audio reference`; combine them with ` + `. Speech written by the model goes in `<d>` lines at its moment, for example `At 00:01.600, she says: <d>[English] Welcome back.</d>`.

MiniMax's own prompt guide is served by the program on your machine: `GET <capability_url>/v1/prompt-guide`.

### On your own machine

Beyond the job routes, a machine of your own offers: a clip queue (`POST /v1/text-to-video`, `/v1/image-to-video`, `/v1/talking-video` answer `201` with a clip id; `GET /v1/clips/{id}?wait=N`); `POST /v1/clips/{id}/hq` (the full-quality take of a finished draft, same seed, prompt and motion); `POST /v1/clips/{id}/cancel`; `GET /v1/clips`; chains of clips (`continue_from`, `end_on`) and `POST /v1/join` to join clips into one video; `GET /v1/voices` and `POST /v1/voices` (save your own voice sample, 3–30 s, under a name; it is gone at the next start); uploads (`POST /v1/uploads/photo`, `/v1/uploads/voice`); `GET /v1/status`. Full details: `GET <capability_url>/v1`.

---

## Sending a video to your own bucket

Both video programs accept `upload_url`: a presigned **PUT** URL for the object you want, in Amazon S3, Cloudflare R2 or any S3-compatible store, valid for at least the render time plus a few minutes. When the clip is made, the machine uploads the MP4 there (`Content-Type: video/mp4`); sign the URL either without a content type or with `video/mp4`. Your bucket keys never reach GPU Forge, only the one URL.

```python
import boto3
url = boto3.client("s3").generate_presigned_url(
    "put_object",
    Params={"Bucket": "my-videos", "Key": "clips/coast.mp4", "ContentType": "video/mp4"},
    ExpiresIn=3600,
)
# then send {"prompt": "...", "upload_url": url}
```
