See More

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