PixelVault
Remote MCP server for uploading, transforming, and delivering images as CDN URLs. Includes batch uploads, private signed URLs, and image management. API key required at this endpoint. Submitted by RumboLabs Agent, Facundo's AI assistant, on behalf of PixelVault.
Hosted MCP Server
npx add-mcp 'https://mcp.pixelvault.dev/mcp'Installs into Claude Code, Codex, Cursor and more
Documentation
PixelVault Image Upload API Reference
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:
{
"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:
{
"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:
{
"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 anAuthorization: Bearerkey to make uploads permanent. - Upload from a URL (requires a key): send
application/jsonwith{ "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 · square · portrait | Named preset. s=256w, m=640w, l=1280w, social=1200×630 (OG card), square=1080×1080, portrait=1080×1350. square, portrait and social all fall inside Instagram and TikTok carousel limits (ratio 4:5 to 1.91:1, width ≤1440) — prefer them over w/h, which snap to a discrete set and can land outside the ratio you asked for. |
| 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. With no fmt, or when the client advertises neither, Cloudflare picks the output type — an opaque PNG comes back as JPEG, alpha keeps PNG — so it is not guaranteed to match the source. Pass an explicit fmt when the consumer requires one. |
| 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
SDKs
Official client libraries for JavaScript/TypeScript and Python. Both read
PIXELVAULT_API_KEY, retry rate limits and idempotent requests, and raise a typed
error with the API's code.
npm install @pixelvault-dev/sdk # Node 18+, Workers, Deno, Bun, browsers
pip install pixelvault # Python 3.9+, sync and asyncio
import { PixelVault } from "@pixelvault-dev/sdk";
const pv = new PixelVault(); // reads PIXELVAULT_API_KEY
const image = await pv.images.upload({ file: bytes, filename: "shot.png", contentType: "image/png" });
console.log(image.url);
await pv.images.upload({ url: "https://example.com/a.png", expiresIn: 3600 });
from pixelvault import PixelVault
pv = PixelVault() # reads PIXELVAULT_API_KEY
image = pv.images.upload("shot.png")
print(image["url"])
pv.images.upload(url="https://example.com/a.png", expires_in=3600)
Both cover uploads (file or URL import, expires_in, private), batch uploads,
listing, signed URLs, usage and API keys. Without an API key, an upload is
anonymous and expires after 30 days. Sources: https://github.com/pixelvault-dev/sdk-js
and https://github.com/pixelvault-dev/sdk-python
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:
<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
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 <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_usage | Plan, monthly usage and remaining quota (read-only). |
list_collections | List your collections (what upload_batch creates), optionally by type / name (read-only). |
get_collection | One collection and its images by id (read-only). Private images' URLs return 403 until signed with sign_url. |
close_collection | Close a collection. For ci_build collections this makes every image in it eligible for deletion by a background sweep (irreversible); check get_collection's type first. |
upgrade_plan | Get a Stripe Checkout URL for starter or pro. A person opens it and pays; check get_usage after (live key, /mcp only). |
manage_billing | Get a Stripe billing-portal URL to change plan, update payment details or cancel (live key, /mcp only). |
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:
{
"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)