# 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":"[email protected]"}'
```
Response:
```json
{
"data": {
"account_id": "acct_abc123",
"email": "[email protected]",
"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 "[email protected]"
```
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.
```

```
## 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": "", "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 [email protected] --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 ` | 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
```
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
``. 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 `` (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)