# PixelVault

> Agent-first image hosting API. Upload images via API, get instant CDN URLs.
> Free tier: 200 MB storage, 500 uploads/month, 1 GB bandwidth. No credit card required.

## API Base URL

https://api.pixelvault.dev

## Authentication

All endpoints 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.

## Endpoints

### Register (no auth required)
POST /v1/auth/register
Body: {"email": "you@example.com"}  (password is OPTIONAL — omit it for a passwordless agent account)
Returns: account, default project, API key (shown only once), and password_set (boolean)
Note: a password is only needed for the web dashboard; the API key authenticates everything else. Set one later via the password-reset flow if you want dashboard login. Use a real email — disposable/temp-mail domains are rejected, and unverified accounts are capped at 5 uploads.

### Upload image
POST /v1/images
Content-Type: multipart/form-data
Form field: file (binary, required), folder (string, optional)
Supported formats: JPEG, PNG, GIF, WebP, AVIF, SVG
Returns: image ID, CDN URL, mime type, size, dimensions

Keyless quickstart: POST /v1/images works WITHOUT an API key. A keyless upload is
temporary (expires in 30 days) — the response includes "temporary": true and
"expires_in". Add an API key (Authorization: Bearer) to make uploads permanent.
No account or key needed for your first upload.

### Upload from a URL (requires an API key)
POST /v1/images
Content-Type: application/json
Body: {"url": "https://...", "folder": "optional"}
Fetches the image server-side and stores it (https only). Keyed uploads only.

### List images
GET /v1/images?page=1&per_page=20
Returns: paginated list of images with metadata

### Get image
GET /v1/images/:id
Returns: image metadata and CDN URL

### Delete image
DELETE /v1/images/:id
Returns: success confirmation

### Batch upload (requires an API key)
POST /v1/images/batch
Content-Type: application/json
Body: {"collection": {"type": "ci_build", "name": "run-123", "visibility": "private"},
       "images": [{"data": "<base64>", "filename": "diff.png"}]}
Uploads up to 50 images in one call, grouped into a COLLECTION. Partial-failure
tolerant (each item reports "ok"). Idempotent by (type, name). visibility "private"
returns a signed URL per image; "public" returns a plain CDN URL. Optional per
collection: expires_in (image TTL), sign_expires_in (signature lifetime, default 7
days), metadata.

### Private images (signed URLs)
Private images are served behind an HMAC-signed URL — strip the token and the CDN
returns 403. The free plan includes up to 100 private images; paid plans are
unlimited. Public hosting is always unlimited.
POST /v1/images/:id/sign-url  Body: {"expires_in": 3600}  (60..2592000 s, default 1h)
Mints a fresh signed URL for an existing image. Deleting the image revokes its URLs.

### Collections
A collection is a typed group of images (a CI run, a generation set, an album).
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 on close)

### Export all images (requires an API key)
POST /v1/export                    start an export → 202 {job_id, status, image_count, status_url}
GET  /v1/export-jobs/:id           poll status (pending|processing|complete|failed|expired); adds download_url when complete
GET  /v1/export-jobs/:id/download  stream the finished archive (application/x-tar) once complete
Downloads EVERY image in the project plus a manifest.json (ids, filenames, storage keys, URLs,
metadata) as one tar archive. Free on every plan — your data is always portable, no lock-in.
Async: start, poll until complete, then download. Archives expire 24h after they're built.
Project-scoped: secret key, or a dashboard token with ?project_id=. CLI: `pixelvault export`.

### CI screenshots (GitHub Action)
For GitHub Actions, the PixelVault screenshots action wraps the batch endpoint: on a
failed run it uploads Playwright/visual-regression screenshots and posts a sticky
comment to the pull request. uses: pixelvault-dev/screenshots-action@v1
Marketplace: https://github.com/marketplace/actions/pixelvault-upload-screenshots
Guide: 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 —
append params to the image URL and the edge resizes, crops, and converts it,
caching the result globally.

Base: https://img.pixelvault.dev/{project_id}/{image_id}.{ext}

Params:
  size       Named preset: s (256w), m (640w), l (1280w), social (1200x630 OG card),
             square (1080x1080), portrait (1080x1350). square/portrait/social are all
             inside Instagram & TikTok carousel limits (ratio 4:5..1.91:1, width<=1440);
             use them rather than w/h, which snap to a discrete set and will miss the ratio.
  w, h       Width/height in px. Snapped up to a discrete set (16..4000) for cache efficiency.
  fit        scale-down (default), contain, cover, crop, pad
  fmt        webp, avif, jpg, png, or auto (negotiates WebP/AVIF via Accept). Omitted or
             unnegotiated, CF picks the output type (an opaque PNG becomes JPEG; alpha keeps
             PNG) - it is NOT guaranteed to be the source format. Pass an explicit fmt if the
             consumer requires one (e.g. WhatsApp accepts only JPEG/PNG).
  q          60, 75, 85, or auto (default)
  segment    foreground — removes the background (AI cut-out) → transparent output. Forces PNG unless a solid background is set or fmt=webp/avif is requested.
  background Fill behind a removed background (or the fit=pad area): hex (%23ffaa00), rgb()/rgba(), or a common CSS color name (white, black).
  gravity    Where to crop toward with fit=cover/crop: auto, face, left, right, top, bottom, or XxY (0.0-1.0). 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). 1 is a good default for downscaled images; 0 = off.
  rotate     90, 180, or 270 degrees.
  flip       h, v, or 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       Watermark: the filename of another RASTER image in the SAME project (png/jpg/webp/avif, e.g. img_logo.png — with extension, optionally folder-prefixed), tiled edge-to-edge at its native size over the base. Bake opacity into the source PNG. Not an external URL and not SVG — must be your own uploaded raster image. A missing filename is ignored (base served).

Examples:
  .../a1b2c3d4e5f6.jpg?w=400                      400px-wide thumbnail
  .../a1b2c3d4e5f6.jpg?size=social                1200x630 social/OG card
  .../a1b2c3d4e5f6.jpg?size=portrait&fmt=jpg      1080x1350 JPEG, Instagram/TikTok 4:5 slide
  .../a1b2c3d4e5f6.jpg?w=800&fit=cover&fmt=webp   800px cover-cropped WebP
  .../a1b2c3d4e5f6.jpg?segment=foreground         background removed → transparent PNG cut-out
  .../a1b2c3d4e5f6.jpg?segment=foreground&background=white   subject on a white fill
  .../a1b2c3d4e5f6.jpg?saturation=0               grayscale
  .../a1b2c3d4e5f6.jpg?w=800&blur=30&saturation=0 grayscale, blurred 800px thumbnail
  .../a1b2c3d4e5f6.jpg?tile=img_logo.png          tiled watermark from your own image

Notes: invalid params are ignored (the original is served, never an error).
SVG sources are served as-is (not transformed). Available on all plans. Background
removal, face-crop, effects, and watermark (segment, gravity, zoom, blur, sharpen,
rotate, flip, brightness, contrast, saturation, tile) apply to project images, not
the anonymous playground.

## MCP Server

PixelVault has a remote MCP (Model Context Protocol) server for AI agents:

  https://mcp.pixelvault.dev/mcp        (transport: streamable-http; API key)
  https://mcp.pixelvault.dev/mcp/oauth  (transport: streamable-http; OAuth 2.1, for ChatGPT apps / OAuth clients — no key to paste)

Tools: upload_image (base64 or source_url; optional expires_in for auto-expiring
uploads), upload_batch (1–50 images grouped into a collection; visibility "private"
returns a signed URL per image), sign_url (mint a signed URL for a private image by
id), transform_image (build a resize/crop/format/background-removal/effects/watermark
URL from an image url or id), list_images, get_image, delete_image, rescue_imgur (scan
a page for hotlinked Imgur images, get a rescue URL for each; no API key needed).
Authenticate at /mcp by sending your API key as a Bearer token in the Authorization
header, or use the /mcp/oauth endpoint (OAuth 2.1) for a per-user key with no paste.

## ChatGPT Custom GPT Action

A trimmed OpenAPI spec for import as a ChatGPT Custom GPT Action (single server,
JSON only — GPT Actions can't send file uploads):

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

In the GPT editor: Configure > Actions > Import from URL, then Authentication >
API Key > Bearer with a pv_live_ key. Actions: upload_image (import a public
image by its URL), list_images, get_image, delete_image.

## Agent Skill

PixelVault ships a skill for Claude Code, Codex, Cursor, and any agent that reads SKILL.md files.
Install it with:

  npx skills add pixelvault-dev/skill

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

## Paste & Host (browser widget)

Let end users paste, drop, or select an image in a textarea and get a hosted CDN
URL inserted automatically (like GitHub's markdown editor), from your own site.
Runs in the browser with a publishable key — no server code.

Publishable keys use the prefix `pv_pub_`. Create one in the dashboard with an
allowlist of origins. They are upload-only (POST /v1/images) and rejected from
any origin not on the allowlist, so they are safe to ship in browser code.

Drop-in script tag:
  <script src="https://pixelvault.dev/paste.js" data-pv-key="pv_pub_xxxx" data-pv-target="textarea"></script>

npm packages:
  @pixelvault-dev/paste        Framework-agnostic core: attachPaste(el, { publishableKey })
  @pixelvault-dev/paste-react  usePaste(ref, options) hook
  @pixelvault-dev/paste-vue    usePaste(elRef, options) composable + v-paste directive

attachPaste / usePaste attach to a <textarea> or <input> only. For rich-text
editors (TipTap, ProseMirror, Lexical, Quill, contenteditable) use the
lower-level uploadImage() from @pixelvault-dev/paste to upload the file yourself,
then insert an image node with the returned CDN url:
  import { uploadImage } from "@pixelvault-dev/paste";
  const { url } = await uploadImage(file, { publishableKey: "pv_pub_xxxx" });
  // returns { id, url, mimeType, size }; throws PixelVaultPasteError on failure

## Resources

- OpenAPI 3.1 spec: https://api.pixelvault.dev/openapi.json
- ChatGPT Custom GPT Action spec: https://pixelvault.dev/openapi-actions.json
- Paste & host widget: https://pixelvault.dev/paste.js
- MCP server: https://mcp.pixelvault.dev/mcp
- Agent skill: https://github.com/pixelvault-dev/skill
- Documentation: https://pixelvault.dev/docs/
- Pricing: https://pixelvault.dev/pricing/
