# PixelVault Docs

> Agent-first image hosting API. Upload images via API, get instant CDN URLs.
> This is the Markdown twin of https://pixelvault.dev/docs — the same content an
> agent can consume without parsing HTML. Base URL: https://api.pixelvault.dev

## Quickstart

Get your first image hosted in 30 seconds. No signup form, no dashboard — just
API calls. Prefer to try it in a browser first? Use the image-to-URL tool at
https://pixelvault.dev/tools/image-to-url.

### 1. Register an account

```
npx pixelvault-cli register
```

Or with curl (password is optional — omit it for a passwordless agent account):

```
curl -X POST https://api.pixelvault.dev/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com"}'
```

Response:

```json
{
  "data": {
    "account_id": "acct_abc123",
    "email": "you@example.com",
    "email_verified": false,
    "password_set": false,
    "plan": "free",
    "default_project": {
      "id": "proj_xyz789",
      "name": "Default",
      "api_keys": { "live": "pv_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }
    }
  }
}
```

Save your API key — it's shown only once.

### 2. Upload an image

```
npx pixelvault-cli upload screenshot.png
```

Or with curl:

```
curl -X POST https://api.pixelvault.dev/v1/images \
  -H "Authorization: Bearer pv_live_xxxxxxxx" \
  -F "file=@screenshot.png"
```

Response:

```json
{
  "data": {
    "id": "img_abc123",
    "url": "https://img.pixelvault.dev/proj_xyz789/img_abc123.png",
    "visibility": "public",
    "mime_type": "image/png",
    "size": 245000,
    "filename": "screenshot.png",
    "folder": null,
    "created_at": "2026-07-15T12:00:00.000Z"
  }
}
```

### 3. Use the URL

The CDN URL is live immediately. Use it in Markdown, HTML, or anywhere you need
an image.

```
![Screenshot](https://img.pixelvault.dev/proj_xyz789/img_abc123.png)
```

## Authentication

All API requests (except registration) require a Bearer token:

```
Authorization: Bearer pv_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

API keys use the prefix `pv_live_` for production and `pv_test_` for test
environments. Keys are hashed server-side — the raw key is never stored.

## API Reference

Base URL: `https://api.pixelvault.dev`. All responses are JSON. Successful
responses wrap the payload in a top-level `data` object; errors return an
`error` object instead:

```json
{
  "error": {
    "code": "error_code",
    "message": "Human-readable description"
  }
}
```

### POST /v1/auth/register

Create a new account. Returns an account, a default project, and an API key. The
password is **optional** — omit it for a passwordless account (ideal for agents);
the owner can set one later via the reset flow to enable dashboard login.

| Field    | Type              | Required |
| -------- | ----------------- | -------- |
| email    | string            | Yes      |
| password | string (8+ chars) | No       |

### POST /v1/images

Upload an image. Accepts `multipart/form-data` (a file) or, with an API key,
`application/json` (a URL).

| Field      | Type              | Required |
| ---------- | ----------------- | -------- |
| file       | binary            | Yes\*    |
| folder     | string            | No       |
| expires_in | integer (seconds) | No       |

Supported formats: JPEG, PNG, GIF, WebP, AVIF, SVG.

- **Auto-expiring uploads:** pass `expires_in` (60–2,592,000 seconds, i.e. 1
  minute to 30 days) to have the image deleted automatically. The response echoes
  `"expires_in"` and `"temporary": true`.
- **Keyless quickstart:** this endpoint works *without* an API key — no account
  needed for your first upload. Keyless uploads are temporary
  (`"temporary": true`, expire in 30 days); add an `Authorization: Bearer` key to
  make uploads permanent.
- **Upload from a URL** (requires a key): send `application/json` with
  `{ "url": "https://…" }` instead of a file. The image is fetched server-side
  (https only) and stored.

### GET /v1/images

List images in your project. Paginated.

| Param  | Type    | Default |
| ------ | ------- | ------- |
| limit  | integer | 50      |
| offset | integer | 0       |

### GET /v1/images/:id

Get a single image's metadata by ID.

### DELETE /v1/images/:id

Delete an image. Removes from storage and CDN.

### POST /v1/images/batch

Upload many images in one request, grouped into a **collection** (e.g. one CI
run). Keyed only. Partial-failure tolerant — each item reports `ok`
independently, so one bad image doesn't sink the batch. Re-running with the same
`(type, name)` upserts the same collection, so it's idempotent.

```
POST /v1/images/batch
{
  "collection": { "type": "ci_build", "name": "run-123", "visibility": "private" },
  "images": [ { "data": "<base64>", "filename": "diff.png" } ]
}
```

`collection.visibility: "private"` returns a **signed URL** per image (see
below); `"public"` returns a plain CDN URL. Optional: `expires_in` (image TTL),
`sign_expires_in` (signature lifetime, default 7 days), and freeform `metadata`
on the collection and per image. Up to 50 images per request.

### Private images & signed URLs

Private images are served behind an HMAC-signed URL: the link works while the
signature is valid, but strip the token and the CDN returns `403`. Ideal for CI
screenshots and any image you don't want on the public, crawlable web. The free
plan includes up to **100 private images**; paid plans are unlimited. Public
hosting is always unlimited.

**POST /v1/images/:id/sign-url** — mint a fresh signed URL for an existing image.
Body: `{ "expires_in": 3600 }` (60–2,592,000 s, default 1 hour). Deleting the
image revokes its URLs.

### Collections

A collection is a typed group of images (a CI run, a generation set, an album)
with shared metadata and its own lifecycle.

- `GET /v1/collections?type=&name=` — list your collections.
- `GET /v1/collections/:id` — a collection and its images.
- `GET /v1/images?collection_id=…` — filter images by collection.
- `POST /v1/collections/:id/close` — close a collection (CI collections reap
  their images on close).

### Export all images

Download **every image in a project** — plus a `manifest.json` mapping ids,
filenames, storage keys, URLs, and metadata — as a single `.tar` archive. Free on
every plan: your data is always portable, no lock-in. A large project is
assembled in the background, so it's a start → poll → download flow.
Project-scoped: use a secret key, or a dashboard token with `?project_id=`.

```
POST /v1/export
→ 202 { "job_id": "exp_…", "status": "pending", "image_count": 327,
        "status_url": "/v1/export-jobs/exp_…" }

GET /v1/export-jobs/:id
→ { "status": "processing", "processed_count": 120, "image_count": 327 }
   # when status is "complete", the response adds "download_url"

GET /v1/export-jobs/:id/download   # streams application/x-tar once complete
```

Poll the status URL until `status` is `complete` (`pending` → `processing` →
`complete`, or `failed`/`expired`), then download. Archives expire 24 hours after
they're built. The CLI (`pixelvault export`) and the dashboard both wrap this in
one step.

### CI screenshots (GitHub Action)

For GitHub Actions, the PixelVault screenshots action
(https://github.com/marketplace/actions/pixelvault-upload-screenshots) wraps the
batch endpoint: on a failed run it uploads your Playwright / visual-regression
screenshots and posts a sticky comment to the pull request. Walkthrough:
https://pixelvault.dev/blog/host-playwright-ci-screenshots.

## Image transforms

Every CDN URL supports on-the-fly transforms via query params — no extra API
call. The edge resizes, crops, and converts the image, then caches the result
globally. Available on all plans.

```
https://img.pixelvault.dev/proj_xyz789/img_abc123.jpg?w=400&fit=cover&fmt=webp
```

| Param      | Values                                       | Notes                                                                                                                                   |
| ---------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| size       | s · m · l · social                           | Named preset. s=256w, m=640w, l=1280w, social=1200×630 (OG card).                                                                        |
| w, h       | pixels                                       | Width/height. Snapped up to a discrete set (16–4000) for cache efficiency.                                                              |
| fit        | scale-down · contain · cover · crop · pad    | Default `scale-down` never enlarges a smaller source; `cover`/`crop`/`contain`/`pad` fit the exact box and may upscale.                 |
| fmt        | webp · avif · jpg · png · auto               | `auto` negotiates WebP/AVIF from the `Accept` header. Omit to keep the source format.                                                   |
| q          | auto · 60 · 75 · 85                          | Output quality. Default `auto`.                                                                                                          |
| segment    | foreground                                   | Removes the background (AI cut-out) → transparent output. Forces PNG unless you set a solid `background` or request `fmt=webp`/`avif`.   |
| background | hex · rgb()/rgba() · named color             | Fills behind a removed background (or the `fit=pad` area). Hex (`%23ffaa00`), `rgb()`/`rgba()`, or a common CSS color name.              |
| gravity    | auto · face · left · right · top · bottom · XxY | Where to crop toward, with `fit=cover`/`crop`. `face` is face-aware.                                                                  |
| zoom       | 0.0–1.0                                      | Face-crop tightness — with `gravity=face` (which needs `fit=cover`/`crop`).                                                             |
| blur       | 0–250                                        | Gaussian blur. Snapped to a discrete set. `0` = off.                                                                                     |
| sharpen    | 0–10                                         | Sharpen strength. Snapped to a discrete set. `1` is a good default for downscaled images; `0` = off.                                    |
| rotate     | 90 · 180 · 270                               | Rotate in 90° steps.                                                                                                                     |
| flip       | h · v · hv                                   | Mirror horizontally, vertically, or both.                                                                                               |
| brightness | multiplier                                   | Snapped to `0.5 · 0.75 · 1.25 · 1.5 · 2`. `1` = no change.                                                                               |
| contrast   | multiplier                                   | Snapped to `0.5 · 0.75 · 1.25 · 1.5 · 2`. `1` = no change.                                                                               |
| saturation | multiplier                                   | Snapped to `0 · 0.5 · 1.5 · 2`. `0` = grayscale; `1` = no change.                                                                        |
| tile       | image filename                               | Watermark. The filename of another raster image in the same project, tiled edge-to-edge at native size. Must be your own image, not SVG. |

Background removal isolates the subject and makes the background transparent:

```
.../img_abc123.jpg?segment=foreground                    # transparent PNG cut-out
.../img_abc123.jpg?segment=foreground&background=white   # subject on a white fill
.../img_abc123.jpg?w=400&h=400&fit=cover&gravity=face&segment=foreground   # portrait cut-out
```

Effects (blur, sharpen, rotate, flip, brightness, contrast, saturation) stack and
combine with resize/crop:

```
.../img_abc123.jpg?blur=30                       # blurred
.../img_abc123.jpg?saturation=0                  # grayscale
.../img_abc123.jpg?rotate=90&flip=h              # rotated + mirrored
.../img_abc123.jpg?w=800&blur=30&saturation=0    # grayscale, blurred 800px thumbnail
```

Watermark tiles one of your own images over the base — upload the watermark once,
then reference it by filename:

```
.../img_abc123.jpg?tile=img_logo.png             # your logo, tiled at native size
.../img_abc123.jpg?w=1200&tile=watermarks/brand.png   # resized + watermarked
```

Background removal, face-crop, effects, and watermark apply to your project
images, not the anonymous playground. Invalid params are ignored — you get the
original image back, never an error. SVG sources are served as-is (not
transformed).

## CLI

The `pixelvault-cli` package (https://www.npmjs.com/package/pixelvault-cli) gives
you one-liner uploads from any terminal. Designed for AI coding agents — URLs go
to stdout, messages to stderr.

```
npm install -g pixelvault-cli
```

Or use directly with `npx`:

```
npx pixelvault-cli register          # Create account, stores API key
npx pixelvault-cli register --email you@example.com --passwordless  # Headless/agent signup
npx pixelvault-cli upload photo.jpg  # Prints CDN URL to stdout
npx pixelvault-cli list              # One URL per line
npx pixelvault-cli get img_abc       # Print URL, or download with -o (and -t to transform)
npx pixelvault-cli delete img_abc    # Silent on success
npx pixelvault-cli export            # Download all your images as a .tar (manifest + every image)
```

`upload` options — bulk-upload with a shell glob, organize into a folder, upload
privately (signed URL), or emit the full JSON:

```
npx pixelvault-cli upload *.png --folder icons          # Bulk upload (shell glob), grouped in a folder
npx pixelvault-cli upload shot.png --json               # Full JSON response instead of the bare URL
npx pixelvault-cli upload secret.png --private          # Private signed URL (needs a secret key)
npx pixelvault-cli upload secret.png --private --expires 24h  # …with a 24h link lifetime (default 7d)
```

For CI/CD and headless agent usage, set `PIXELVAULT_API_KEY`:

```
export PIXELVAULT_API_KEY=pv_live_xxx
npx pixelvault-cli upload screenshot.png
```

Source: https://github.com/pixelvault-dev/cli

## Claude Code skill

PixelVault ships a Claude Code skill (https://github.com/pixelvault-dev/skill) so
your agent can upload images without leaving the conversation.

```
claude plugin add pixelvault-dev/skill
```

Once installed, four skills are available:

| Skill                        | Description                                                    |
| ---------------------------- | ------------------------------------------------------------- |
| `/pixelvault-upload <file>`  | Upload image(s), get CDN URLs                                  |
| `/pixelvault-setup`          | Install CLI and configure API key                             |
| `/pixelvault-list`           | List recent uploads                                           |
| `/pixelvault-transform`      | Resize, convert, remove backgrounds, add effects or watermark |

The upload skill can also be triggered automatically — when Claude sees "upload
this screenshot" or needs to host an image, it invokes the skill without you
typing the command. The skill wraps `pixelvault-cli`, so install that first (or
run `/pixelvault-setup`). Source: https://github.com/pixelvault-dev/skill

## Paste & host (browser widget)

Let your own users paste, drop, or select an image in a textarea and get a hosted
CDN URL inserted automatically — the same flow as GitHub's Markdown editor, on
your site. It runs entirely in the browser with a **publishable key**, so there's
no server code to write.

### 1. Create a publishable key

In your dashboard (https://pixelvault.dev/dashboard), open a project's
**Publishable keys** section and add one with an allowlist of the origins it may
be used from (for example `https://app.example.com`). Publishable keys
(`pv_pub_…`) are safe to ship in browser code: they are **upload-only** and
rejected from any origin that isn't on the allowlist.

### 2a. Drop-in script tag

Zero build step. Add one tag; every field matching `data-pv-target` becomes
paste-and-host:

```html
<textarea data-pixelvault></textarea>
<button data-pv-pick="[data-pixelvault]">Upload image</button>
<script src="https://pixelvault.dev/paste.js"
        data-pv-key="pv_pub_xxxxxxxx"
        data-pv-target="[data-pixelvault]"></script>
```

Attributes on the tag: `data-pv-key` (required), `data-pv-target` (CSS selector;
defaults to `[data-pixelvault]`), and optional `data-pv-endpoint` /
`data-pv-folder`. Add a `data-pv-pick` attribute to any button (its value is the
target field's selector) to open the file picker on click. The script also
exposes a global `window.PixelVaultPaste` with `attach`, `openFilePicker`, and
`init`.

### 2b. npm package

For bundled apps, install the framework-agnostic core:

```
npm install @pixelvault-dev/paste
```

```js
import { attachPaste, openFilePicker } from "@pixelvault-dev/paste";

const field = document.querySelector("textarea");
const options = { publishableKey: "pv_pub_xxxxxxxx" };

attachPaste(field, options);                       // paste + drop
uploadButton.addEventListener("click", () => openFilePicker(field, options));
```

Framework bindings are available as `@pixelvault-dev/paste-react` (`usePaste`
hook) and `@pixelvault-dev/paste-vue`.

### 3. What the user sees

Pasting, dropping, or picking an image inserts an `![Uploading…]()` placeholder
at the caret, uploads the file, then swaps in
`![name](https://img.pixelvault.dev/…)`. Override the inserted text (HTML,
BBCode, a bare URL) with the `render` option, and hook `onUploadStart`,
`onUploadComplete`, and `onError` for progress and error handling.

## Images in email (Resend / react-email)

Gmail doesn't render base64 `data:` images and many Outlook clients don't either,
so every image in an email has to point at a public URL. Resend sends the email
but doesn't host images, so host them on PixelVault first and reference the
returned URL. Because it's a permanent, immutable CDN URL on zero-egress storage,
it renders for the life of the inbox and costs the same no matter how many times
the email is opened.

Host each asset **once** (a prep script or CI step) with your secret API key and
save the permanent URL — don't call it per send, or you'll mint a new URL and
burn an upload every time. Then pass the saved URL into your react-email template
as a prop and render it with `<Img src={`${logoUrl}?w=240&fmt=auto`} width="120"
/>` (serve 2× and constrain with `width` for crisp retina — transform params
resize on the fly). Full walkthrough:
https://pixelvault.dev/blog/host-images-in-resend-emails.

## MCP server

PixelVault runs a remote Model Context Protocol server, so any MCP-capable agent
(Claude, Cursor, and others) can host images directly. It's hosted on
Cloudflare's edge at:

```
https://mcp.pixelvault.dev/mcp   (transport: streamable-http)
```

Tools exposed:

| Tool              | Description                                                                                             |
| ----------------- | ------------------------------------------------------------------------------------------------------ |
| `upload_image`    | Upload an image (base64 `data` or a public `source_url`) → instant CDN URL. Optional `expires_in`.     |
| `upload_batch`    | Upload 1–50 images in one call, grouped into a collection. `visibility: "private"` returns signed URLs. |
| `sign_url`        | Mint a time-limited signed URL for a private image by `id`.                                             |
| `transform_image` | Build an on-the-fly transform URL (resize, crop, format, background removal, effects, watermark).       |
| `list_images`     | List your images (paginated).                                                                          |
| `get_image`       | Get metadata + CDN URL for one image.                                                                  |
| `delete_image`    | Delete one image.                                                                                       |
| `rescue_imgur`    | Scan a page for hotlinked Imgur images and get a rescue URL for each (no API key needed).              |

Authenticate by sending your API key as a Bearer token. Add it to Claude Code:

```
claude mcp add --transport http pixelvault https://mcp.pixelvault.dev/mcp \
  --header "Authorization: Bearer pv_live_xxxxxxxx"
```

Or configure any client that supports the `streamableHttp` transport:

```json
{
  "mcpServers": {
    "pixelvault": {
      "type": "streamable-http",
      "url": "https://mcp.pixelvault.dev/mcp",
      "headers": { "Authorization": "Bearer pv_live_xxxxxxxx" }
    }
  }
}
```

**OAuth (for ChatGPT apps and other OAuth clients):** connect to `https://mcp.pixelvault.dev/mcp/oauth` instead — no key to paste. You authorize through a short email-code consent screen and PixelVault mints a per-user key behind the scenes. The `/mcp` endpoint above (API key) stays available for existing clients.

## ChatGPT (Custom GPT Action)

Add PixelVault to a ChatGPT [Custom GPT](https://help.openai.com/en/articles/8554397-creating-a-gpt) as an Action, so the GPT can host and manage images. Import this trimmed OpenAPI spec — built specifically for GPT Actions (single server, JSON only):

```
https://pixelvault.dev/openapi-actions.json
```

In the GPT editor, open **Configure → Actions → Import from URL** and paste the URL above. Then set **Authentication → API Key**, choose **Bearer**, and paste a `pv_live_` key from your dashboard.

Four actions are available:

| Action | Description |
|--------|-------------|
| `upload_image` | Import a public image by its `url` → permanent CDN URL |
| `list_images` | List your images |
| `get_image` | Get metadata + CDN URL for one image |
| `delete_image` | Delete one image |

GPT Actions can't send file uploads, so `upload_image` takes a public image **URL** rather than a file. Once hosted, apply [transforms](#image-transforms) by appending query params to the returned URL. Need base64 uploads or all eight tools? Use the [MCP server](#mcp-server) instead.

## Agent discovery

PixelVault provides standard discovery endpoints for any AI agent:

- `/.well-known/api-catalog` — API catalog for agent discovery
- `/llms.txt` — LLM-readable service description (lists the MCP server)
- `/openapi.json` — OpenAPI 3.1 specification
- `/openapi-actions.json` — trimmed spec for ChatGPT Custom GPT Actions
- MCP server — `https://mcp.pixelvault.dev/mcp` (API key) or `/mcp/oauth` (OAuth)
