---
type: AI Model
id: image-dpi-api
title: Image DPI API
provider: Pixazo
description: "Prepare an image for print. Send image_url with a dpi between 72 and 1200 (300 by default) and the file is re-tagged at its own pixel size, keeping the source format for PNG and JPEG — PNG is re-tagged by copying the metadata chunk, so the pixels are never decoded and never re-compressed. Add a size preset (4x6, 5x7, 8x10, 11x14, 12x18, 18x24, 24x36, a5, a4, a3, a2, letter, tabloid) or width_in / height_in to target a physical size instead, and allow_upsample to permit enlarging. Anything other than the native re-tag comes back as TIFF unless you pass format. An image with too few pixels for the size you asked for is rejected rather than silently stretched."
resource: https://www.pixazo.ai/models/image-dpi-api
docs_url: https://www.pixazo.ai/models/image-dpi-api
latest_version: v1
tags:
  - image-dpi
  - pixazo
variants:
  - id: media-image-dpi-v1
    name: Image DPI 1.0
    version: 1.0
    capabilities:
      - Prepare for Print
timestamp: 2026-09-19T12:37:58.999Z
---

# Image DPI API

> Provider: **Pixazo**
> Source: https://www.pixazo.ai/models/image-dpi-api

Prepare an image for print. Send image_url with a dpi between 72 and 1200 (300 by default) and the file is re-tagged at its own pixel size, keeping the source format for PNG and JPEG — PNG is re-tagged by copying the metadata chunk, so the pixels are never decoded and never re-compressed. Add a size preset (4x6, 5x7, 8x10, 11x14, 12x18, 18x24, 24x36, a5, a4, a3, a2, letter, tabloid) or width_in / height_in to target a physical size instead, and allow_upsample to permit enlarging. Anything other than the native re-tag comes back as TIFF unless you pass format. An image with too few pixels for the size you asked for is rejected rather than silently stretched.

## Image DPI 1.0

### Prepare for Print

## Base URL

```
https://gateway.pixazo.ai/media-tools/v1/image-dpi
```

## Authentication

All requests require an API key passed via header.

**Pricing:** Billed at **$0.002 per image** — a flat rate per call. The DPI you ask for, the print size and the pixel dimensions of the source make no difference to the price.

**Retries:** this is an asynchronous job on shared encoding capacity, so a request can occasionally come back `processing_failed` or take much longer than usual. These are transient and succeed on a retry, and a failed job is **never charged** — the wallet hold is released. If you chain these tools, retry a failed step rather than failing the whole pipeline.

Header

Type

Required

Description

Ocp-Apim-Subscription-Key

string

Yes

Your API subscription key

## Image DPI & Print Prep generate request

## Request Code

HTTP Python JavaScript cURL

```
POST https://gateway.pixazo.ai/media-tools/v1/image-dpi
Content-Type: application/json
Cache-Control: no-cache
Ocp-Apim-Subscription-Key: YOUR_API_KEY

{
  "image_url": "https://api-assets.pixazo.ai/media-api-test/sample.png",
  "dpi": 300,
  "size": "8x10",
  "format": "tiff"
}
```

```
import requests

url = "https://gateway.pixazo.ai/media-tools/v1/image-dpi"
headers = {
    "Content-Type": "application/json",
    "Cache-Control": "no-cache",
    "Ocp-Apim-Subscription-Key": "YOUR_API_KEY"
}
data = {
  "image_url": "https://api-assets.pixazo.ai/media-api-test/sample.png",
  "dpi": 300,
  "size": "8x10",
  "format": "tiff"
}

response = requests.post(url, json=data, headers=headers)
print(response.json())
```

```
const url = "https://gateway.pixazo.ai/media-tools/v1/image-dpi";
const headers = {
  "Content-Type": "application/json",
  "Cache-Control": "no-cache",
  "Ocp-Apim-Subscription-Key": "YOUR_API_KEY"
};
const data = {
  "image_url": "https://api-assets.pixazo.ai/media-api-test/sample.png",
  "dpi": 300,
  "size": "8x10",
  "format": "tiff"
};

fetch(url, {
  method: "POST",
  headers: headers,
  body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => console.log(data));
```

```
curl -X POST "https://gateway.pixazo.ai/media-tools/v1/image-dpi" \
  -H "Content-Type: application/json" \
  -H "Cache-Control: no-cache" \
  -H "Ocp-Apim-Subscription-Key: YOUR_API_KEY" \
  --data-raw '{
    "image_url": "https://api-assets.pixazo.ai/media-api-test/sample.png",
    "dpi": 300,
    "size": "8x10",
    "format": "tiff"
  }'
```

## Output

```
{
  "request_id": "media-image-dpi_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "status": "QUEUED",
  "polling_url": "https://gateway.pixazo.ai/v2/requests/status/media-image-dpi_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```

[Try Now](https://api.pixazo.ai/api-details#api=media-tools&operation=image-dpi)

## Webhook (Optional)

Add the `X-Webhook-URL` header to your generate request to receive a POST callback instead of polling.

```
X-Webhook-URL: https://your-server.com/webhook/callback
```

## Request Parameters - Image DPI & Print Prep generate request

Parameter

Required

Type

Default

Allowed values / range

Description

image\_url

Yes

string

—

HTTP(S) URL to an image

Publicly reachable HTTP(S) URL of the image to re-tag or resample.

dpi

No

number

`300`

72 – 1200

Dots per inch written into the output. 300 is the usual number for photo printing.

format

No

string

`tiff` (native mode keeps a PNG or JPG source)

`tiff`, `tif`, `png`, `jpg`, `jpeg`

Output format. The backend default is **TIFF**. The one exception is native mode — no `size` and no `width_in`/`height_in` — where a **PNG** source comes back a PNG and a **JPEG** source a JPG. Every other case, including a native-mode WebP, TIFF or HEIC source, produces TIFF unless you pass this parameter.

size

No

string

—

`4x6`, `5x7`, `8x10`, `11x14`, `12x18`, `18x24`, `24x36`, `a5`, `a4`, `a3`, `a2`, `letter`, `tabloid`

Named print size. Sending it switches the call from a metadata re-tag to a resample at that exact physical size.  
Every named size renders at the default `dpi` of 300 — the largest, `24x36`, is 77.8 MP against an **80 MP output ceiling**. That ceiling is what a high `dpi` runs into: above roughly **304** DPI for `24x36`, **430** for `18x24` and **454** for `a2`, the job comes back `FAILED` naming the megapixel count.

width\_in

No

number

—

0.1 – 100

Print width in inches, for a size that is not on the named list. Use together with `height_in` instead of `size`.

height\_in

No

number

—

0.1 – 100

Print height in inches. Use together with `width_in`.

allow\_upsample

No

boolean

`false`

`true`, `false`

Permit the resample to invent pixels the source does not have. Off by default, which is why an under-resolution request comes back **422** instead of a soft-looking print.

**Native mode — re-tag only.** Send just `image_url` and `dpi` and the image is re-tagged at its own pixel size, pixels untouched. A **PNG** source keeps its format through a metadata-only chunk copy that never re-encodes, so it comes back bit-for-bit identical apart from its density header; a **JPEG** source comes back a JPG. **Every other case produces TIFF** unless you pass `format` — a native-mode source that is neither PNG nor JPEG, and every resample-mode request. Pass `format` to pin the output type either way.

**Resample mode.** Add a `size`, or an explicit `width_in` + `height_in`, and the image is resampled to that exact physical size at your `dpi`. The fit is **cover-cropped**, so the print is filled edge to edge and the overhanging side is trimmed rather than letterboxed. Alpha is flattened onto **white**, and EXIF — including GPS coordinates — is stripped. A TIFF output keeps its ICC profile; a PNG output does not.

**It will not quietly upscale for you.** If the source has too few pixels for the print you asked for, the API answers **422** and tells you the numbers — the pixel size it has and the pixel size the job needs — rather than stretching a small file into a soft print. Send `allow_upsample: true` if you want it to go ahead anyway.

**Ceilings.** The output area is capped at **24 megapixels**, roughly 12×18 in at 300 DPI; a request over that is refused as a **400 before any charge**, so probing the limit is free. Source images above **120 megapixels** are refused, and the job comes back `FAILED` with code `invalid_input`. The single exception is the zero-decode path, which is **PNG only**: a native-mode re-tag that keeps a PNG at its own pixel size and returns PNG is a metadata chunk copy, so it works at any source size. Anything else — a JPEG, TIFF or WebP source, or a PNG you asked to convert or resample — is handed to ImageMagick, which allocates per source pixel, and is subject to the same 120 MP limit. A second ceiling applies to what the pipeline _renders_: above **80 megapixels** of output the job is refused as well, which in practice you only meet in native mode on a very large non-PNG source.

## Example Request

```
{
  "image_url": "https://api-assets.pixazo.ai/media-api-test/sample.png",
  "dpi": 300,
  "size": "8x10",
  "format": "tiff"
}
```

## Response

```
{
  "request_id": "media-image-dpi_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "status": "QUEUED",
  "polling_url": "https://gateway.pixazo.ai/v2/requests/status/media-image-dpi_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```

## Request Headers

Header

Value

Content-Type

application/json

Cache-Control

no-cache

Ocp-Apim-Subscription-Key

YOUR\_API\_KEY

## Response Handling

Common status codes.

Code

Meaning

202

Accepted — Request queued

400

Bad Request

401

Unauthorized

402

Insufficient Balance

403

Forbidden

422

Unprocessable — source has too few pixels for the requested print size

429

Too Many Requests

500

Internal Server Error

## Error Responses

Queue system errors and model validation errors.

### Queue System Errors

```
// 402 — Insufficient balance
{
  "error": "Insufficient Balance",
  "message": "Your wallet does not have enough balance."
}
```

```
// 400 — Model not found
{
  "error": "Model not found",
  "message": "Model 'media-image-dpi' not found or is disabled"
}
```

```
// 400 — a parameter is outside its allowed range.
// Settled at the gateway from your parameters alone, BEFORE any wallet hold,
// so probing the limits costs nothing.
{
  "error": "Invalid request parameters",
  "message": "'dpi' must be <= 1200 (got 5000)",
  "violations": ["'dpi' must be <= 1200 (got 5000)"]
}
```

### Error via Status/Webhook

```
{
  "request_id": "media-image-dpi_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "status": "ERROR",
  "model_id": "media-image-dpi",
  "error": "Description of the error",
  "output": null
}
```

### Print Verdicts

Three refusals are decided **after** the image has been fetched and its header read, so they are not submit-time errors: the submit returns `202` as usual and the verdict arrives on the **polled status response** as a `FAILED` job. The `error` string carries the code `invalid_input` followed by our sentence for that verdict, with the numbers for your image in it.

```
// GET /v2/requests/status/{request_id}
// Source has too few pixels for the print, and allow_upsample was not set
{
  "request_id": "media-image-dpi_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "status": "FAILED",
  "model_id": "media-image-dpi",
  "error": "invalid_input: The source is 1200x1600 px, which is only 150 DPI at 8x10 in — printing that size at 300 DPI needs 2400x3000 pixels. Upscale the image first, ask for a smaller physical size, or send allow_upsample: true to accept a softer print.",
  "output": null
}
```

Verdict

When it fires

The sentence you get

**Not enough pixels**

The source cannot cover the requested physical size at your `dpi`, and `allow_upsample` is not `true`.

`The source is 1200x1600 px, which is only 150 DPI at 8x10 in — printing that size at 300 DPI needs 2400x3000 pixels. Upscale the image first, ask for a smaller physical size, or send allow_upsample: true to accept a softer print.`

**Source over 120 MP**

Any request that has to decode the source. Checked before the output ceiling, so a very large source reports this one. This is the one verdict whose sentence comes straight from the pipeline.

`Image is 14000x10000 (140 MP); the print pipeline reads up to 120 MP. A PNG can be re-tagged at its own size instead.`

**Output over 80 MP**

Reached in **native mode** on a large non-PNG source, where the output is the source’s own pixel grid. A request that names a `size` or inches reaches it too — `24x36` at 400 DPI is 138 MP.

`That size and DPI would render 11000x9000 pixels; this endpoint renders up to 80 MP (24x36 in at 300 DPI). Ask for a smaller physical size or a lower dpi.`

The numbers in each sentence are computed for _your_ image, so treat the examples above as shapes rather than fixed strings — match on the `invalid_input` code, not on the wording.

## Retrieving Results

Poll the universal status endpoint to check progress and retrieve results.

### Endpoint

```
GET https://gateway.pixazo.ai/v2/requests/status/{request_id}
Ocp-Apim-Subscription-Key: YOUR_API_KEY
```

## cURL Example

```
curl -H "Ocp-Apim-Subscription-Key: YOUR_API_KEY" \
  "https://gateway.pixazo.ai/v2/requests/status/media-image-dpi_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
```

## Response (Completed)

```
{
  "request_id": "media-image-dpi_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "status": "COMPLETED",
  "model_id": "media-image-dpi",
  "error": null,
  "output": {
    "media_url": [
      "https://pub-582b7213209642b9b995c96c95a30381.r2.dev/v1/media-image-dpi_019dxxxx/output.tiff"
    ],
    "media_type": "image/tiff"
  },
  "created_at": "2026-03-31T10:00:00.000Z",
  "updated_at": "2026-03-31T10:00:15.000Z",
  "completed_at": "2026-03-31T10:00:15.000Z"
}
```

## Response Fields

Field

Type

Description

request\_id

string

Unique request identifier

status

string

QUEUED, PROCESSING, COMPLETED, FAILED, or ERROR

model\_id

string

Model that processed the request

error

string|null

Error message if failed

output.media\_url

array

URLs to generated media (R2 CDN)

output.media\_type

string

MIME type of the output

created\_at

string

When request was created

completed\_at

string

When request completed

polling\_url

string

Status URL (initial response only)

## Status Values

Status

Description

QUEUED

Request accepted, waiting to be processed

PROCESSING

Being processed by the model

COMPLETED

Done — output contains the result

FAILED

Failed — check error field

ERROR

System error — not charged

## Status Flow

```
QUEUED → PROCESSING → COMPLETED
                    → FAILED
                    → ERROR
```

## Typical Workflow

1.  **Send a generate request** to the API endpoint
2.  **Save the `request_id`** from the response
3.  **Poll** every 5-10 seconds: `GET /v2/requests/status/{request_id}`
4.  **When `status` is `"COMPLETED"`**, download from `output.media_url`

**Tip:** Use `X-Webhook-URL` header to get a callback instead of polling.
