Quickstart
Get your first image hosted in 30 seconds. No signup form, no dashboard — just API calls. Prefer to try it in your browser first? Drop an image into the free image-to-URL tool.
1. Register an account
Response:
"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"
}
}
}
}
2. Upload an image
Response:
"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 — we never store the raw key.
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:
"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 |
|---|---|---|
| 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 wraps the batch endpoint: on a failed run it uploads your Playwright / visual-regression screenshots and posts a sticky comment to the pull request. See the walkthrough.
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 (white, black, …). |
| 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 (png/jpg/webp/avif, e.g. img_logo.png, optionally folder-prefixed), tiled edge-to-edge at native size. Bake opacity into the source PNG. Must be your own image — not an external URL, not SVG. A missing filename is ignored. |
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 (segment, gravity, zoom, blur, sharpen, rotate, flip, brightness, contrast, saturation, tile) 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 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, no password
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: github.com/pixelvault-dev/cli
Claude Code skill
PixelVault ships a Claude Code 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 a 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 the pixelvault-cli, so make sure it's installed first (or run /pixelvault-setup).
Source: 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, 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:
<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 for wiring elements added later.
2b. npm package
For bundled apps, install the framework-agnostic core:
npm install @pixelvault-dev/paste 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)); Or a framework binding:
// npm install @pixelvault-dev/paste-react
import { useRef } from "react";
import { usePaste } from "@pixelvault-dev/paste-react";
function Editor() {
const ref = useRef(null);
const { openFilePicker } = usePaste(ref, { publishableKey: "pv_pub_xxxxxxxx" });
return (
<>
<textarea ref={ref} />
<button onClick={openFilePicker}>Upload image</button>
</>
);
} <!-- npm install @pixelvault-dev/paste-vue -->
<script setup>
import { ref } from "vue";
import { usePaste } from "@pixelvault-dev/paste-vue";
const field = ref(null);
const { openFilePicker } = usePaste(field, { publishableKey: "pv_pub_xxxxxxxx" });
</script>
<template>
<textarea ref="field" />
<button @click="openFilePicker">Upload image</button>
</template> 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.
A tiny helper uploads a local asset and hands back the CDN URL (server-side — it uses your secret API key):
// email-images.ts — host a local asset once, get a permanent CDN URL.
import { readFile } from "node:fs/promises";
import { basename } from "node:path";
const API = "https://api.pixelvault.dev/v1/images";
export async function hostForEmail(path: string): Promise<string> {
const form = new FormData();
form.append("file", new Blob([await readFile(path)]), basename(path));
const res = await fetch(API, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PIXELVAULT_API_KEY}` },
body: form,
});
if (!res.ok) throw new Error(`PixelVault upload failed: ${res.status}`);
const { data } = await res.json(); // the URL is nested under `data`
return data.url; // https://img.pixelvault.dev/proj_abc/img_xyz.png
} The URL is permanent, so host each asset once — a prep script or CI step — and save the URL (an env var works). Don't call this per send, or you'll mint a new URL and burn an upload every time:
// prep.ts — run ONCE (a script or CI step). The URL is permanent,
// so host the asset a single time and save the URL, e.g. to an env var.
import { hostForEmail } from "./email-images";
const logoUrl = await hostForEmail("./assets/logo.png");
console.log(logoUrl); // → store as EMAIL_LOGO_URL; never re-host per send Pass that saved URL into your react-email template as a prop and render it with <Img>. Serve a 2× image and constrain it with width for crisp retina — the transform params resize on the fly:
// receipt.tsx — the hosted URL comes in as a prop (no per-render upload).
import { Img } from "@react-email/components";
export function Receipt({ logoUrl }: { logoUrl: string }) {
// Serve at 2× (w=240), display at 120 — crisp on retina.
return <Img src={`${logoUrl}?w=240&fmt=auto`} width="120" alt="Acme" />;
} Then render the template to HTML and send it (this file is .tsx — it contains JSX):
// send.tsx — render with the pre-hosted URL and hand the HTML to Resend.
import { Resend } from "resend";
import { render } from "@react-email/render";
import { Receipt } from "./receipt";
const resend = new Resend(process.env.RESEND_API_KEY);
const logoUrl = process.env.EMAIL_LOGO_URL!; // hosted once, saved earlier
await resend.emails.send({
from: "receipts@yourdomain.com",
to: "customer@example.com",
subject: "Your receipt",
html: await render(<Receipt logoUrl={logoUrl} />),
}); Not on react-email? The same idea works with any templating: call hostForEmail() once, save the URL, then interpolate it into your <img src>. See the full walkthrough.
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) Eight tools are exposed:
| Tool | Description |
|---|---|
upload_image | Upload an image (base64 data or a public source_url) → instant CDN URL. Optional expires_in for an auto-expiring image |
upload_batch | Upload 1–50 images in one call, grouped into a collection. visibility: "private" returns a signed URL per image. Partial-failure tolerant |
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, AI background removal, effects, watermark) from an image url or id |
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 with:
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:
{
"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 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 by appending query params to the returned URL.
Need base64 uploads or all eight tools? Use the 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)