# Inference API

Source: https://docs.prodia.com/reference/inference/

The inference API provides an HTTP endpoint for running jobs to convert text to
images, an image to an image, text to a video, and
[more](https://app.prodia.com/explorer). The jobs endpoint covers all types of
supported inference workloads through a unified IO interface and a single
`/v2/job` endpoint. Job requests are made in a synchronous manner with the
input containing the job configuration along with the input data and the output
containing an updated job configuration with the output data.

## Authentication

API requests require authentication via the [`Authorization`
header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization)
with a `Bearer` token scheme:

```
Authorization: Bearer xxxxxxxxxxxx
```

Tokens are created on the [API Dashboard](https://app.prodia.com/api) and are
required to use the API.

> Did you know?
>
> Prodia's API tokens are JWTs. This allows us to check your identity without
> touching a database and without adding latency.

## Base URL

The base URL for all requests should be: <https://inference.prodia.com>

## `POST /v2/job` Execute a Job

Jobs are executed by posting to the `/v2/job` endpoint with an appropriate job
configuration and input data.

### Query Parameters

#### `price` (optional)

When set to `true`, the response job result will include a `price` field with
the billing product code and cost in dollars.

```
POST /v2/job?price=true
```

```json title="price field in job result"
{
  "price": {
    "product": "inference-flux-fast-schnell-txt2img-v2",
    "dollars": 0.0010
  }
}
```

### Job Configuration

All requests minimally have a [JSON](https://www.json.org/json-en.html) job
configuration that is differentiated on the `type` field:

```json title="job.json"
{
	"type": "inference.ping.v1"
}
```

There are a variety of job types documented in the
[explorer](https://app.prodia.com/explorer) and they have the following in
common:

- All jobs have a `type` field that indicates which job type is being
  requested.
- Jobs with configuration have a `config` field that contains the type specific
  job configuration.

For example, this is a job configuration for a [FLUX.1
\[schnell\]](https://huggingface.co/black-forest-labs/FLUX.1-schnell) text to image
generation:

```json title="job.json"
{
	"type": "inference.flux-fast.schnell.txt2img.v2",
	"config": {
		"prompt": "grainy photograph of a space explorer"
	}
}
```

> note:
>
> Some jobs require configuration with some minimal information (e.g.
> `inference.flux-fast.schnell.txt2img.v2` requires at least a config with a prompt. Make
> sure to check the job configuration requirements in the
> [explorer](https://app.prodia.com/explorer).

### Job Input Data

Some jobs need data that isn't best transferred in the JSON format (e.g. binary
[PNG](https://en.wikipedia.org/wiki/PNG) data). This additional non-JSON input
to the job execution is sent as a
[multipart/form-data](https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types#multipartform-data)
part named `input`. Multiple input data parts may be specified as long as they
use different filenames. When sending input data the job configuration is sent
in the part named `job` with `filename="job.json"` (and there must only be one part with this name).

For example, a FLUX.1 \[schnell] image to image generation requires an input image.
This would result in a multipart/form-data request with 2 parts (one for the
job configuration and another for the input image). Given the following job
configuration and an `input.jpg` in the local directory:

```json title="job.json"
{
	"type": "inference.flux.schnell.img2img.v1",
	"config": {
		"prompt": "grainy photograph of a space explorer",
		"loras": ["prodia/lora/flux/levels/analog@v1"]
	}
}
```

Curl can be used to make the multipart request:

```bash title="curl FLUX.1 [schnell] image to image"
curl -H "Authorization: Bearer $PRODIA_TOKEN" \
     -H 'Accept: image/jpeg' \
     -F job=@job.json \
     -F input=@input.jpg \
     --output output.jpg \
     'https://inference.prodia.com/v2/job'
```

An HTTP trace of the request might look something like:

```text {9,21} "name="job"" "name="input""
POST /v2/job HTTP/2
Host: inference.prodia.com
User-Agent: curl/8.11.1
Accept: image/jpeg
Authorization: Bearer $PRODIA_TOKEN
Content-Type: multipart/form-data; boundary=------------------------fYf8kg9C7fC50PWcYjMuWt

--------------------------fYf8kg9C7fC50PWcYjMuWt
Content-Disposition: form-data; name="job"; filename="job.json"
Content-Type: application/json

{
    "type": "inference.flux.schnell.img2img.v1",
    "config": {
        "prompt": "grainy photograph of a space explorer",
        "loras": ["prodia/lora/flux/levels/analog@v1"]
    }
}

--------------------------fYf8kg9C7fC50PWcYjMuWt
Content-Disposition: form-data; name="input"; filename="input.jpg"
Content-Type: image/jpeg

......JFIF......
...more bytes...
--------------------------fYf8kg9C7fC50PWcYjMuWt--
```

> note:
>
> Note the part `name` in the `Content-Disposition` above. The job part name must
> be `job` with `filename="job.json"`, and the input part name(s) must be `input`.
> Only one `job` part is allowed. Multiple `input` parts may be provided (if the
> job type supports it) as long as the `filename` is unique for each `input` part.

### Job Result

All jobs return a job result which is a mirror of the original job
configuration with additional information from the job execution. A job result
includes all fields in the [job configuration](https://docs.prodia.com/reference/inference/#job-configuration) and the
following:

- `created_at` is the UTC time the server created the job
- `updated_at` is the UTC time the job result was last updated
- `expires_at` is the UTC time after which the job is considered expired
- `id` is a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier)
  generated by the server to identify this job
- `state` has a `current` field indicating the final state of the job
  and an optional `history` array (see [Job States](https://docs.prodia.com/reference/inference/#job-states) below)
- `metrics` contains the elapsed inference time for the job and additional
  metrics when appropriate (e.g. iterations per second)
- `error` is an error message present if the final state of the job is `failed`
- `deprecated` is present when the job type has been deprecated (see
  [Deprecation](https://docs.prodia.com/reference/inference/#deprecation) below)
- `price` is present when `?price=true` is set (see [Query Parameters](https://docs.prodia.com/reference/inference/#query-parameters) above)

The job result may also update the `config` field to include default values or
results produced during inference. For example, a text-to-image job may write
back the random `seed` that was used, the actual `width` and `height` after
any rounding, or an [NSFW](https://en.wikipedia.org/wiki/Not_safe_for_work)
classification result. These fields are useful for reproducibility (re-submit
the same config with the same seed to get identical output) and for inspecting
what parameters the server actually used.

> tip:
>
> To reproduce a job, copy the `config` from the job result and submit it as a
> new job — the server-assigned defaults (like `seed`) are preserved.

For example, using the job configuration above would render a job result
similar to this:

```json title="job.json"
{
	"type": "inference.flux.schnell.img2img.v1",
	"created_at": "2025-01-01T00:00:14.885Z",
	"updated_at": "2025-01-01T00:00:20.11Z",
	"expires_at": "2025-01-01T00:01:14.885Z",
	"id": "c83d7027-240a-484a-aeba-96014e568711",
	"state": {
		"current": "completed"
	},
	"config": {
		"prompt": "grainy photograph of a space explorer",
		"loras": ["prodia/lora/flux/levels/analog@v1"]
	},
	"metrics": {
		"elapsed": 5.138920783996582,
		"ips": 4.864834670706344
	}
}
```

### Job States

The `state.current` field of a job result will be one of:

| State        | Description                                        |
| ------------ | -------------------------------------------------- |
| `created`    | Job has been received and is queued for processing |
| `processing` | Job is currently being processed by a worker       |
| `completed`  | Job completed successfully                         |
| `failed`     | Job encountered an error (see `error` field)       |

The `state` object may also include a `history` array that records each state
transition with timestamps and dwell times. State history is available to
admin-scoped tokens:

```json title="state with history"
{
  "state": {
    "current": "completed",
    "history": [
      {
        "from": "created",
        "to": "processing",
        "at": "2025-01-01T00:00:14.900Z",
        "dwell": 0.015,
        "message": "Job is being processed."
      },
      {
        "from": "processing",
        "to": "completed",
        "at": "2025-01-01T00:00:20.110Z",
        "dwell": 5.21,
        "message": "Job completed."
      }
    ]
  }
}
```

For non-admin tokens, `state.history` is `null` and only `state.current` is
populated.

### Deprecation

When a job type is being retired, the job result will include a `deprecated`
field:

```json title="deprecated field in job result"
{
  "deprecated": {
    "on": "2025-12-01T00:00:00Z",
    "eol": "2026-01-01T00:00:00Z",
    "notice": "This job type is deprecated. Please migrate to inference.flux-fast.schnell.txt2img.v3"
  }
}
```

- `on` is the date the deprecation was announced
- `eol` (end-of-life) is the date after which the job type will no longer be
  accepted
- `notice` contains migration guidance

Before the `eol` date, the job will still execute normally but the
`deprecated` field serves as a warning. After the `eol` date, the server will
reject the job with a `400 Bad Request` and the `deprecated` field in the
error response.

### Input Constraints

Job types may define constraints on input files such as maximum file size and
image dimensions. These constraints are documented per job type in the
[explorer](https://app.prodia.com/explorer). Submitting input that exceeds
these constraints will result in a `400 Bad Request` response.

### Job Output Data

Similar to [job input data](https://docs.prodia.com/reference/inference/#job-input-data), all jobs support returning a
`multipart/form-data` response that includes the job result JSON as the `job`
part and output data as the `output` parts.

An HTTP trace of such a response might look something like:

```text {6,30} "name="job"" "name="output""
HTTP/2 200
content-type: multipart/form-data; boundary=b3d4fb976dce6e5d036fa7fb3da645bcfcc56384abb2cb618f8c8bdfd360
x-request-id: 835b0174-5a7c-45e0-8256-050d0f2c47a1

--b3d4fb976dce6e5d036fa7fb3da645bcfcc56384abb2cb618f8c8bdfd360
Content-Disposition: form-data; name="job"; filename="job.json"
Content-Type: application/json

{
  "type": "inference.flux.schnell.img2img.v1",
  "created_at": "2025-01-01T00:00:14.885Z",
  "updated_at": "2025-01-01T00:00:20.11Z",
  "expires_at": "2025-01-01T00:01:14.885Z",
  "id": "c83d7027-240a-484a-aeba-96014e568711",
  "state": {
    "current": "completed"
  },
  "config": {
    "prompt": "grainy photograph of a space explorer",
    "loras": [
      "prodia/lora/flux/levels/analog@v1"
    ]
  },
  "metrics": {
    "elapsed": 5.138920783996582,
    "ips": 4.864834670706344
  }
}
--b3d4fb976dce6e5d036fa7fb3da645bcfcc56384abb2cb618f8c8bdfd360
Content-Disposition: form-data; name="output"; filename="f9774fd02a85-8985-4d2a-a093-24bda78322b7"
Content-Type: image/jpeg

......JFIF......
...more bytes...
--b3d4fb976dce6e5d036fa7fb3da645bcfcc56384abb2cb618f8c8bdfd360--
```

> note:
>
> Note the part `name` in the `Content-Disposition` above. There will always be
> at least a `job` part. Zero or more `output` parts may be sent depending on the
> job type. All `output` parts will have unique filenames.

### Content Negotiation

#### Request Body

The HTTP request format is specified via the request `Content-Type` header. All
jobs can accept a `multipart/form-data` request. If a job type doesn't (or
optionally doesn't) accept job input data then the `Content-Type` can be set to
`application/json` and the job configuration can be sent directly.

#### Response Body

The desired HTTP response format is specified via the request `Accept` header.
All jobs can negotiate a `multipart/form-data` response which works much like
[job input data](https://docs.prodia.com/reference/inference/#job-input-data) except that instead of `input` parts it has
`output` parts. If a job only outputs a single job output data file, then it
can be returned directly by setting the `Accept` header to one of the supported
output formats for the job type.

> tip:
>
> Content negotiation allows some jobs to be simplified into a request containing
> only a job configuration and a response containing only the job output data.
> For example, FLUX.1 \[schnell] text to image jobs can be curl'd like so:
>
> ```bash title="curl FLUX.1 [schnell] text to image"
> curl -H "Authorization: Bearer $PRODIA_TOKEN" \
>      -H 'Accept: image/jpeg' \
>      --json '{"type": "inference.flux-fast.schnell.txt2img.v2", "config": {"prompt": "grainy photograph of a space explorer"}}' \
>      --output output.jpg \
>      'https://inference.prodia.com/v2/job'
> ```

When negotiating a `multipart/form-data` response the default output content
type can be overridden by specifying a secondary type in the `Accept` header.
For example `Accept: multipart/form-data; image/png` would format the response
into a `multipart/form-data` where the first `output` part has `Content-Type:
image/png`.

#### Output Format Parameters

The `Accept` header supports encoding parameters to control output quality and
compression. Parameters are specified using the standard MIME type parameter
syntax: `type/subtype;param1=value1;param2=value2`.

##### image/jpeg

| Parameter     | Type   | Range       | Default | Description                                                |
| ------------- | ------ | ----------- | ------- | ---------------------------------------------------------- |
| `quality`     | int    | 0-95        | 75      | Compression quality (higher = better quality, larger file) |
| `optimize`    | bool   | 0/1         | false   | Optimize Huffman tables for smaller file size              |
| `progressive` | bool   | 0/1         | false   | Create progressive JPEG (loads in multiple passes)         |
| `subsampling` | string | 444/422/420 | 420     | Chroma subsampling (444 = best quality, 420 = smallest)    |

Example:

```bash
curl -H "Authorization: Bearer $PRODIA_TOKEN" \
     -H 'Accept: image/jpeg;quality=90;progressive=1' \
     --json '{"type": "inference.flux-fast.schnell.txt2img.v2", "config": {"prompt": "puppies"}}' \
     --output output.jpg \
     'https://inference.prodia.com/v2/job'
```

##### image/webp

| Parameter       | Type | Range | Default | Description                                                |
| --------------- | ---- | ----- | ------- | ---------------------------------------------------------- |
| `quality`       | int  | 0-100 | 80      | Compression quality (higher = better quality, larger file) |
| `lossless`      | bool | 0/1   | false   | Use lossless compression (ignores quality setting)         |
| `alpha-quality` | int  | 0-100 | 100     | Quality of alpha channel compression                       |
| `method`        | int  | 0-6   | 4       | Compression method (0 = fastest, 6 = best compression)     |

Example:

```bash
curl -H "Authorization: Bearer $PRODIA_TOKEN" \
     -H 'Accept: image/webp;quality=95;method=6' \
     --json '{"type": "inference.flux-fast.schnell.txt2img.v2", "config": {"prompt": "puppies"}}' \
     --output output.webp \
     'https://inference.prodia.com/v2/job'
```

##### image/png

| Parameter        | Type | Range | Default | Description                               |
| ---------------- | ---- | ----- | ------- | ----------------------------------------- |
| `compress-level` | int  | 0-9   | 6       | Compression level (0 = none, 9 = maximum) |
| `optimize`       | bool | 0/1   | false   | Optimize encoding for smaller file size   |

Example:

```bash
curl -H "Authorization: Bearer $PRODIA_TOKEN" \
     -H 'Accept: image/png;compress-level=9;optimize=1' \
     --json '{"type": "inference.flux-fast.schnell.txt2img.v2", "config": {"prompt": "puppies"}}' \
     --output output.png \
     'https://inference.prodia.com/v2/job'
```

### Status Codes

#### `200` OK

A [`200` status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200)
indicates the job was completed successfully.

#### `400` Bad Request

A [`400` status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400)
indicates that the request was malformed. If possible the server will respond
with a job result with the state set to `failed` and a message regarding the
error in the `error` field:

```json title="400 error response"
{
  "type": "inference.flux-fast.schnell.txt2img.v2",
  "id": "c83d7027-240a-484a-aeba-96014e568711",
  "created_at": "2025-01-01T00:00:14.885Z",
  "updated_at": "2025-01-01T00:00:14.886Z",
  "expires_at": "2025-01-01T00:01:14.885Z",
  "state": {
    "current": "failed"
  },
  "config": {},
  "error": "config: prompt is required"
}
```

#### `401` Unauthorized

A [`401` status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401)
indicates the request requires [authentication](https://docs.prodia.com/reference/inference/#authentication).

#### `403` Forbidden

A [`403` status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403)
indicates that the [authentication](https://docs.prodia.com/reference/inference/#authentication) provided does not have
sufficient privileges for the request. This can happen if the job type requires
additional permissions.

#### `429` Too Many Requests

A `429` status code indicates that there is no idle capacity available at
request time. *This is a normal part of load management.* The response
includes a `Retry-After` header specifying the minimum delay (in seconds)
before retrying.

The server also applies adaptive backoff: if the system remains at capacity,
successive `429` responses may take progressively longer to return. This
server-side delay prevents thundering herd effects and helps the system
recover. Clients should still respect the `Retry-After` header and use
exponential backoff for best results.

> note:
>
> This response code is issued based on *global* system capacity. It does not
> mean that your account has been rate limited or that you individually
> have sent too many requests.

#### `5xx` Server Errors

A status code in the [`5xx` range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#server_error_responses)
indicates a server error. These errors are typically transient and will be
resolved soon.
