# Ideogram 4.0

Best-in-class typography and text rendering, priced per rendering speed.

- Model ID: `ideogram/ideogram-4.0/text-to-image`
- Provider: Ideogram
- Modality: text → image
- Type: Text to Image
- Status: stable
- Delivery: async
- Max resolution: 3328x1248
- Output format: png

## Authentication

Send your API key in the `X-API-Key` header.

The same key reads the org balance at `GET https://api.dev.pika.art/billing/balance`, which is free to call and confirms the request can be paid for before you submit it.

## Endpoint

```
POST https://api.dev.pika.art/v1/media/ideogram/ideogram-4.0/text-to-image
```

## Reference uploads

To use a local file (image, audio, or video) as a model input, first request a presigned upload URL: `POST /v1/media/uploads` with the `X-API-Key` header and a JSON body `{ "content_type": "image/png", "size_bytes": 12345 }`, where `size_bytes` is the file's exact byte length. The response returns `upload_url` (temporary, valid for 5 minutes), `headers`, and `url` (the permanent Pika URL). PUT the file bytes to `upload_url` sending the returned `headers` verbatim — both `Content-Type` and `Content-Length` are signed, and storage returns a bare 403 if either is missing or differs. Then pass `url` into fields such as `image`, `image_url`, `end_image_url`, `image_urls`, `first_frame_image`, or `mask_image_url`.

## Generate

`POST /v1/media/ideogram/ideogram-4.0/text-to-image`

```bash
curl -X POST https://api.dev.pika.art/v1/media/ideogram/ideogram-4.0/text-to-image \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "text_prompt": "An art exhibition poster, real high-key film still life: a single brown egg balanced upright on the tip of a silver spoon, hard sunlight, a long crisp shadow, pale background. Typography: enormous high-contrast serif title \"BALANCE\"; subtitle \"AN EXHIBITION ON WEIGHT & POISE\"; a mono block \"09 APR — 21 JUN · MUSEO LUMEN · HALL 1\". Refined high-end editorial art photography on medium-format film: soft directional natural light, shallow depth of field, luminous painterly colour, authentic real subjects and texture (never glossy stock, never a posed commercial model), quiet minimal composition with generous negative space and one witty idea; sophisticated saturated palette (high-key pastel or deep jewel tones). Purely photographic — no illustration, no 3D render, no neon or LED. Typography is world-class 2026 editorial graphic design: dramatic scale contrast between an oversized display cut and tiny monospaced captions, a confident modern grid, impeccable kerning, refined hierarchy, type woven into the composition — never a plain block of text. 16:9.",
  "rendering_speed": "DEFAULT"
}'
```

### Request body

Exactly one of `text_prompt` or `json_prompt` is required.

| Param | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `text_prompt` | string | no | - | Text Prompt |
| `json_prompt` | object | no | - | The Ideogram 4.0 structured prompt; supplying it disables magic-prompt. |
| `json_prompt.high_level_description` | string | yes | - | High Level Description |
| `json_prompt.style_description` | object | no | - | Style description |
| `json_prompt.style_description.aesthetics` | string | no | - | Aesthetics |
| `json_prompt.style_description.art_style` | string | no | - | Art Style |
| `json_prompt.style_description.lighting` | string | no | - | Lighting |
| `json_prompt.style_description.medium` | string | no | - | Medium |
| `json_prompt.style_description.photo` | string | no | - | Photo |
| `json_prompt.style_description.color_palette[]` | string[] | no | - | Color Palette |
| `json_prompt.compositional_deconstruction` | object | yes | - | Compositional deconstruction |
| `json_prompt.compositional_deconstruction.background` | string | yes | - | Background |
| `json_prompt.compositional_deconstruction.elements[]` | object[] | yes | - | Elements One of 2 item shapes, see below. |
| `rendering_speed` | enum | no | DEFAULT | Rendering Speed |
| `enable_copyright_detection` | boolean | no | - | Enable Copyright Detection |
| `resolution` | enum | no | - |  |

#### json_prompt.compositional_deconstruction.elements[] item — type = obj

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `json_prompt.compositional_deconstruction.elements[].type` | string | yes | Always `obj`. |
| `json_prompt.compositional_deconstruction.elements[].desc` | string | yes | Desc |
| `json_prompt.compositional_deconstruction.elements[].bbox` | array | no | Bbox |
| `json_prompt.compositional_deconstruction.elements[].color_palette[]` | string[] | no | Color Palette |

#### json_prompt.compositional_deconstruction.elements[] item — type = text

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `json_prompt.compositional_deconstruction.elements[].type` | string | yes | Always `text`. |
| `json_prompt.compositional_deconstruction.elements[].text` | string | yes | Text |
| `json_prompt.compositional_deconstruction.elements[].desc` | string | yes | Desc |
| `json_prompt.compositional_deconstruction.elements[].bbox` | array | no | Bbox |
| `json_prompt.compositional_deconstruction.elements[].color_palette[]` | string[] | no | Color Palette |

#### Example body

```json
{
  "text_prompt": "An art exhibition poster, real high-key film still life: a single brown egg balanced upright on the tip of a silver spoon, hard sunlight, a long crisp shadow, pale background. Typography: enormous high-contrast serif title \"BALANCE\"; subtitle \"AN EXHIBITION ON WEIGHT & POISE\"; a mono block \"09 APR — 21 JUN · MUSEO LUMEN · HALL 1\". Refined high-end editorial art photography on medium-format film: soft directional natural light, shallow depth of field, luminous painterly colour, authentic real subjects and texture (never glossy stock, never a posed commercial model), quiet minimal composition with generous negative space and one witty idea; sophisticated saturated palette (high-key pastel or deep jewel tones). Purely photographic — no illustration, no 3D render, no neon or LED. Typography is world-class 2026 editorial graphic design: dramatic scale contrast between an oversized display cut and tiny monospaced captions, a confident modern grid, impeccable kerning, refined hierarchy, type woven into the composition — never a plain block of text. 16:9.",
  "rendering_speed": "DEFAULT"
}
```

### Accepted values

| Param | Values | Default |
| --- | --- | --- |
| `rendering_speed` | `TURBO`, `DEFAULT`, `QUALITY` | `DEFAULT` |
| `resolution` | `2048x2048`, `1440x2880`, `2880x1440`, `1664x2496`, `2496x1664`, `1792x2240`, `2240x1792`, `1440x2560`, `2560x1440`, `1600x2560`, `2560x1600`, `1728x2304`, `2304x1728`, `1296x3168`, `3168x1296`, `1152x2944`, `2944x1152`, `1248x3328`, `3328x1248`, `1280x3072`, `3072x1280`, `1024x3072`, `3072x1024`, `1024x1024`, `896x1120`, `1120x896`, `864x1152`, `1152x864`, `832x1248`, `1248x832`, `800x1280`, `1280x800`, `720x1280`, `1280x720`, `720x1440`, `1440x720`, `512x1536`, `1536x512` | — |
| `json_prompt.compositional_deconstruction.elements[].type` | `obj`, `text` | — |

Returns a job object, not the final output — normally with status `queued`. If you're at your concurrency limit, the job stays `queued` until a slot frees up, then starts on its own. Store the `id` and poll until a terminal state. A rejected submit (insufficient balance, rate limit, unpriceable input) still returns the job object, with status `failed` and `error` set. An `Idempotency-Key` replay returns the existing job in its current state — including `failed`, so retry a failure with a fresh key, never the same one.

### Response

**200 OK · queued**

```json
{
  "id": "media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0",
  "status": "queued"
}
```

## Poll status

`GET /v1/media/jobs/{request_id}`

```bash
curl https://api.dev.pika.art/v1/media/jobs/{request_id} \
  -H "X-API-Key: YOUR_API_KEY"
```

Poll the job by id until it reaches a terminal state: `completed` or `failed`.

### The job object

- `id` (string): Unique identifier for the job.
- `status` (enum): One of `queued`, `running`, `completed`, `failed`.
- `output` (object): Present once the job completes.
  - `media_type` (enum): `image`.
  - `output.images[].url` (string): URLs of the generated images.
- `error` (object): Present if the job failed. `error.code` is the stable machine-readable value to branch on — one of `invalid_input`, `content_moderation`, `provider_error`, `provider_timeout`, `provider_unavailable`, `rate_limited`, `insufficient_balance`, `membership_required`, `cycle_limit_exceeded`, `admission_suspended`, `timed_out`, `internal`. Unrecognized values normalize to `provider_error`. `error.message` is diagnostic text and varies.
- `usage` (object): Units the provider reported for the job, such as `video_output_tokens`, `image_output_tokens`, `input_tokens`, or `output_seconds`. Present once the job is terminal, carries only the units the job reports, and is `null` when nothing was reported. It may not reproduce the charge, because some pricing components are fixed by the request; the charge is authoritative.
- `billing` (object): What the job charged. Present once the job is terminal, `null` on the submit response and on webhooks. `billing.state` is one of `settled`, `pending`, `unavailable`. `settled` carries `charge_micro_usd`, the amount collected in micro-USD (the balance's unit), `0` included. `pending` means the charge is still settling: retry at 1s, 2s, then 5s. `unavailable` means the billing read failed on Pika's side: retry no sooner than 30s. Record spend only when `state` is `settled`, keyed by job id; a `billing` of `null` is not a charge.

### Response

**200 OK · running**

```json
{
  "id": "media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0",
  "status": "running"
}
```

**200 OK · completed**

```json
{
  "id": "media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0",
  "status": "completed",
  "output": {
    "media_type": "image",
    "images": [
      {
        "url": "https://api.dev.pika.art/v1/files/img_8f3a2c91.png",
        "content_type": "image/png"
      }
    ]
  },
  "usage": {
    "image_output_tokens": 1568
  },
  "billing": {
    "state": "settled",
    "charge_micro_usd": 52000
  }
}
```

**200 OK · failed**

```json
{
  "id": "media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0",
  "status": "failed",
  "error": {
    "code": "provider_error",
    "message": "upstream generation failed"
  },
  "billing": {
    "state": "settled",
    "charge_micro_usd": 0
  }
}
```

A completed job carries its download URL in `output`. `GET /v1/media/jobs/{request_id}/content` returns that same URL as `{ "url": "..." }` for clients that only kept the id; it answers `409` until the job completes.

## Errors

Errors use conventional HTTP status codes and always return JSON, in one of two shapes. Request-level errors (auth, unknown path, schema validation, idempotency conflict) are `{"message": "..."}`. Submit rejections that occur after the job row is created (insufficient balance, rate limit, unpriceable input, dispatch unavailable) return the full failed job envelope — branch on `error.code`, and retry with a fresh `Idempotency-Key`, since a failed job replays on its key.

- 401 Unauthorized — The API key is missing or invalid. Body: `{"message":"Invalid API key"}`
- 403 Forbidden — An inactive key is rejected with a `{"message"}` body. A submit that the org balance or postpaid cycle limit cannot cover is rejected after the job row exists and returns the failed job envelope. `GET /billing/balance` separates the two: a `200` means the key is active, so the rejection was the balance or the cycle limit. Body: `{"id":"media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0","status":"failed","error":{"code":"insufficient_balance","message":"Insufficient org balance"}}`
- 404 Not Found — No job with that id exists in your org, or the media path names an unknown vendor/model/function. Body: `{"message":"media job not found"}`
- 409 Conflict — The result was requested before the job completed, or an Idempotency-Key header was reused with a different body. Body: `{"message":"media job is not ready"}`
- 422 Unprocessable Entity — The request body failed validation: an invalid enum value, a missing required field, a wrong type, or malformed JSON. The JSON message names the offending field. A schema-valid parameter combination that cannot be priced instead fails after job creation and returns the failed job envelope with error code `invalid_input`. Body: `{"message":"duration: Input should be less than or equal to 15"}`
- 429 Too Many Requests — You've hit your per-minute or per-day request limit, or your org's job queue is full. Returned as the failed job envelope. If there's a Retry-After header, wait that long, then retry with a fresh Idempotency-Key. Body: `{"id":"media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0","status":"failed","error":{"code":"rate_limited","message":"rate limit exceeded: rpm"}}`
- 503 Service Unavailable — The model rail or a backend dependency is temporarily unavailable. Returned as the failed job envelope; retry with backoff and a fresh Idempotency-Key. Body: `{"id":"media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0","status":"failed","error":{"code":"provider_unavailable","message":"media dispatch unavailable"}}`

## Pricing

- TURBO: $0.0315 per image
- DEFAULT: $0.063 per image
- QUALITY: $0.105 per image

Only successful generations are charged.

Check the balance against this price before submitting: `GET https://api.dev.pika.art/billing/balance` returns `balance_micro_usd`, which is US dollars multiplied by 1,000,000, and `postpaid` when the org bills by invoice instead. For an active postpaid org, compare the price against `postpaid.cycle.remaining_micro_usd` rather than the prepaid balance. Report a shortfall to the user rather than submitting a request the balance cannot cover.

---

Model page: https://dev.pika.art/models/ideogram/ideogram-4.0/text-to-image
API index: https://dev.pika.art/llms.txt
