# Enlarge photos from code

Three ways in, one contract. The **REST API** runs Pro. The **enlarge CLI** and its **local MCP server** run Free on your machine with no key, and Pro with an API key.

## Get an API key

Free needs no key in the CLI or MCP server. Pro needs an account with credits and an API key:

1. [Sign in](https://enlarge.photos/sign-in) with your email. There is no password; we send you a link.
2. Buy a credit pack on your [account page](https://enlarge.photos/account) (see [pricing](https://enlarge.photos/pricing)).
3. On the same page, create an API key under API keys. It starts with `enl_` and is shown once, so store it somewhere safe.

Send it as `Authorization: Bearer enl_…`. You can have up to 10 keys and revoke any of them at any time. Keys spend the credits of the account that made them.

## REST API

Base URL `https://enlarge.photos/api/v1`. Requests and responses are JSON, except the upload (multipart) and the download (the image). Enlarging is asynchronous: create a job, poll it, then download the result.

- Each job costs one credit, any size up to 100 MP. It is taken when the job is created and refunded automatically if the job fails.
- Results are kept for 24 hours, then deleted. We delete the upload when the job ends.
- Rate limits: 30 per minute new jobs and 60 per minute deletes per account, and 20 per minute failed key checks per IP address.

### `POST /api/v1/upscale`

Creates a Pro job. Send `multipart/form-data`; the answer is `202` with the `job`.

| Field | Values | Default |
| --- | --- | --- |
| `image` | JPEG, PNG or WebP file, up to 50 MB | Required |
| `scale` | `2`, `4` | The largest that keeps the output within 100 MP |
| `mode` | Deprecated: accepted and ignored (`standard`, `high_fidelity`, `low_res`) | None |
| `face_enhance` | Deprecated: accepted and ignored (`true`, `false`) | None |
| `format` | `auto`, `jpeg`, `png`, `webp` | `auto` (same as the input) |

```bash
curl https://enlarge.photos/api/v1/upscale \
  -H "Authorization: Bearer enl_…" \
  -F image=@photo.jpg \
  -F scale=4
```

### `GET /api/v1/jobs/:id`

Returns the job. `status` moves from `queued` to `running` to `succeeded` or `failed`, and a succeeded job becomes `expired` after 24 hours. Poll every 2 seconds. Only the account that created a job can see it.

```bash
curl https://enlarge.photos/api/v1/jobs/job_7Hk2mQ9xLp3vR8sT \
  -H "Authorization: Bearer enl_…"
```

200 OK:

```json
{
  "job": {
    "id": "job_7Hk2mQ9xLp3vR8sT",
    "status": "succeeded",
    "engine": "pro",
    "mode": "standard",
    "scale": 4,
    "face_enhance": true,
    "format": "auto",
    "filename": "photo@4x.jpg",
    "input": {
      "width": 1200,
      "height": 800
    },
    "output": {
      "width": 4800,
      "height": 3200
    },
    "credits": 1,
    "output_bytes": 4182733,
    "error": null,
    "created_at": "2026-10-09T17:02:11.000Z",
    "started_at": "2026-10-09T17:02:11.412Z",
    "finished_at": "2026-10-09T17:02:24.903Z",
    "expires_at": "2026-10-10T17:02:24.903Z",
    "download_url": "https://enlarge.photos/api/v1/jobs/job_7Hk2mQ9xLp3vR8sT/download",
    "thumbnail_url": "https://enlarge.photos/api/v1/jobs/job_7Hk2mQ9xLp3vR8sT/thumbnail"
  }
}
```

### `GET /api/v1/jobs/:id/download`

Returns the enlarged image. Before the job succeeds the answer is `409 not_ready`; after 24 hours it is `410 expired`.

```bash
curl https://enlarge.photos/api/v1/jobs/job_7Hk2mQ9xLp3vR8sT/download \
  -H "Authorization: Bearer enl_…" \
  -o photo@4x.jpg
```

### `GET /api/v1/jobs/:id/thumbnail`

Returns a small preview of the result: a WebP image whose longest side is 384 pixels. A succeeded job links to it as `thumbnail_url`. Like the download, it answers `409 not_ready` until the job succeeds and `410 expired` once the result is deleted.

```bash
curl https://enlarge.photos/api/v1/jobs/job_7Hk2mQ9xLp3vR8sT/thumbnail \
  -H "Authorization: Bearer enl_…" \
  -o preview.webp
```

### `DELETE /api/v1/jobs/:id`

Deletes the job and its files now, instead of after 24 hours. The credit is not refunded. While the job is queued or running the answer is `409 not_ready`; wait for it to finish. A job that is already gone, or not yours, is `404 not_found`.

```bash
curl -X DELETE https://enlarge.photos/api/v1/jobs/job_7Hk2mQ9xLp3vR8sT \
  -H "Authorization: Bearer enl_…"
```

200 OK:

```json
{
  "deleted": true
}
```

### `GET /api/v1/jobs?limit=20`

Your most recent jobs, newest first, as `{ "jobs": [...] }`. `limit` is 1 to 100.

```bash
curl "https://enlarge.photos/api/v1/jobs?limit=20" \
  -H "Authorization: Bearer enl_…"
```

### `GET /api/v1/me`

The account the key belongs to and its credit balance.

```bash
curl https://enlarge.photos/api/v1/me \
  -H "Authorization: Bearer enl_…"
```

200 OK:

```json
{
  "email": "you@example.com",
  "credits": 59
}
```

### A full round trip

Create, poll and download in one script (uses `jq`):

enlarge.sh:

```bash
export ENLARGE_API_KEY=enl_…

JOB=$(curl -s https://enlarge.photos/api/v1/upscale \
  -H "Authorization: Bearer $ENLARGE_API_KEY" \
  -F image=@photo.jpg | jq -r .job.id)

# Poll every 2 seconds until the job leaves queued/running
while :; do
  STATUS=$(curl -s https://enlarge.photos/api/v1/jobs/$JOB \
    -H "Authorization: Bearer $ENLARGE_API_KEY" | jq -r .job.status)
  [ "$STATUS" = queued ] || [ "$STATUS" = running ] || break
  sleep 2
done

[ "$STATUS" = succeeded ] && curl -s https://enlarge.photos/api/v1/jobs/$JOB/download \
  -H "Authorization: Bearer $ENLARGE_API_KEY" -o photo-enlarged.jpg
```

## Errors

Every error has the same shape and an HTTP status that matches its code:

402 Payment Required:

```json
{
  "error": {
    "code": "insufficient_credits",
    "message": "This needs 1 credit and you have 0. Buy credits at https://enlarge.photos/account#credits. Questions: support@enlarge.photos."
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| `401` | `unauthorized` | Sign in or send a valid API key (Authorization: Bearer enl_...). |
| `402` | `insufficient_credits` | Not enough credits. Buy credits at https://enlarge.photos/account#credits. Questions: support@enlarge.photos. |
| `403` | `forbidden` | You do not have access to this. |
| `404` | `not_found` | Not found. |
| `409` | `not_ready` | The job has not finished yet. |
| `410` | `expired` | The result has expired. Results are kept for 24 hours. |
| `422` | `too_large` | The image is too large. |
| `422` | `invalid_input` | The request is invalid. |
| `429` | `rate_limited` | Too many requests. Try again in a minute. |
| `502` | `provider_error` | The enlargement failed at our processor. Your credit was refunded. |
| `503` | `unavailable` | Pro is temporarily unavailable. Try again later. |

## CLI

The `enlarge` command ships in the npm package `enlarge-photos` and needs Node 20 or later. Free runs the model on your machine and works offline; on a Mac it uses Core ML when it can.

```bash
npm i -g enlarge-photos

# Free: runs on your machine, offline, no key
enlarge photo.jpg

# Pro: our cloud AI, with your API key, asks before spending credits
export ENLARGE_API_KEY=enl_…
enlarge photo.jpg -e pro
```

### Usage

```text
enlarge <inputs...> [-s 2|4] [-e free|pro] [-f auto|jpeg|png|webp]
        [-o <file|dir>] [--force] [--yes] [--json] [--verbose]
```

Inputs are files or folders (JPEG, PNG and WebP; folders are not searched recursively). Results are named `<name>@<scale>x.<ext>` and existing files are never overwritten without `--force`.

| Option | What it does |
| --- | --- |
| `-s 2\|4` | Scale. Default: the largest that fits the engine's limit. |
| `-e free\|pro` | Engine. Default `free`. |
| `--mode` | Deprecated. Still accepted, and ignored: Pro has no modes. |
| `--no-face-enhance` | Deprecated. Still accepted, and ignored. |
| `-f` | Output format: `auto`, `jpeg`, `png`, `webp`. Default `auto`, same as the input. |
| `-o <file\|dir>` | Where to write the result. |
| `--force` | Overwrite existing files. |
| `--yes` | Skip the Pro credit confirmation. Required when not running in a terminal. |
| `--json` | Print one JSON object per result on stdout. |
| `--verbose` | Show more detail, such as which accelerator ran. |

### Commands

| Command | What it does |
| --- | --- |
| `enlarge login` | Save your API key. Paste it at the hidden prompt or pipe it in; it is checked before saving. |
| `enlarge logout` | Remove the saved key. |
| `enlarge whoami` | Show the account and credit balance for the key. |
| `enlarge mcp` | Start the MCP server on stdio (see below). |
| `enlarge --help` | All options. |

### Configuration and exit codes

- The key comes from `ENLARGE_API_KEY`, else from `~/.config/enlarge-photos/config.json` (written by `enlarge login`, readable only by you; `XDG_CONFIG_HOME` is respected).
- Pro shows the credit total and asks before it spends anything. Progress goes to stderr, results to stdout.
- Exit codes: `0` success, `1` error, `2` usage error, `3` not enough credits.

## MCP server

`enlarge mcp` is a local MCP server on stdio. Your agent hands it a file path; the image is enlarged on your machine (Free) or in the cloud (Pro) and saved as a new file. Without a key, only Free is available.

### Claude Code

```bash
npm i -g enlarge-photos
claude mcp add enlarge -e ENLARGE_API_KEY=enl_… -- enlarge mcp

# Free only, no key:
# claude mcp add enlarge -- enlarge mcp
```

### Claude Desktop

```bash
npm i -g enlarge-photos
```

Then open Settings, Developer, Edit Config, and add this to `claude_desktop_config.json`. Restart Claude Desktop afterward.

claude_desktop_config.json:

```json
{
  "mcpServers": {
    "enlarge": {
      "command": "enlarge",
      "args": ["mcp"],
      "env": { "ENLARGE_API_KEY": "enl_…" }
    }
  }
}
```

### Cursor

```bash
npm i -g enlarge-photos
```

Then add this to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project).

mcp.json:

```json
{
  "mcpServers": {
    "enlarge": {
      "command": "enlarge",
      "args": ["mcp"],
      "env": { "ENLARGE_API_KEY": "enl_…" }
    }
  }
}
```

### Codex

```bash
npm i -g enlarge-photos
```

Then add this to `~/.codex/config.toml`.

config.toml:

```toml
[mcp_servers.enlarge]
command = "enlarge"
args = ["mcp"]
env = { ENLARGE_API_KEY = "enl_…" }
```

### Tools

| Tool | What it does |
| --- | --- |
| `upscale_image` | Enlarges the image at `path` (absolute). Optional: `scale`, `engine`, `format`, `output_path` (`mode` and `face_enhance` are deprecated, accepted and ignored). Returns the output path, its size, the engine, credits used and credits left. |
| `estimate_cost` | What a Pro enlargement of `path` would cost, computed locally. Optional `scale`. |
| `get_account` | The account and credit balance for the configured key. |

Long jobs report progress to clients that ask for it. Try: `Enlarge ~/Desktop/photo.jpg 4x` or `How much would a Pro enlargement of this photo cost?`
