AnonShot

API REST v1

Use AnonShot images in your app

Upload an image from your server, get its code back and use a direct URL in an LLM, an image tag or any HTTP client. Every file is re-oriented, stripped of its EXIF metadata and served as WebP.

How it works in 30 seconds

  1. Create a token in your account settings.
  2. Send the file to POST /api/v1/photos with that token.
  3. Keep the shortUrl, or simply the ready-made URLs returned in the response.
  4. Give publicImageUrl to the LLM if the image is public, or download fileUrl with the token if it must stay private.

1. Create and protect the token

The full token is shown only once. Store it in an environment variable on your backend, never in Git, a URL, JavaScript shipped to the browser or a distributed mobile app.

ANONSHOT_TOKEN=anon_live_…
Authorization: Bearer $ANONSHOT_TOKEN
  • photos:write allows uploading, editing and deleting.
  • photos:read allows listing, details and authenticated downloads.
  • Default quotas are 60 requests per minute and 10,000 requests per month.
  • Tokens can be revoked and can have an expiry date.

2. Upload a photo

Send a multipart/form-data form. The files field can repeat: JPEG, PNG, WebP and GIF are accepted, up to 50 MB per file and 20 files per request.

curl example

curl -X POST https://anonshot.com/api/v1/photos \
  -H "Authorization: Bearer $ANONSHOT_TOKEN" \
  -F "files=@photo.jpg"

Options: add -F "password=a-separate-secret" to protect the page, or -F "burnAfterRead=true" for a one-time image. Don't use these options if an external service needs to read the public URL freely.

Server-side JavaScript example

import { readFile } from "node:fs/promises"

const bytes = await readFile("photo.jpg")
const form = new FormData()
form.append("files", new Blob([bytes], { type: "image/jpeg" }), "photo.jpg")

const response = await fetch("https://anonshot.com/api/v1/photos", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ANONSHOT_TOKEN}`,
  },
  body: form,
})

const payload = await response.json()
if (!response.ok) throw new Error(payload.error?.message ?? "Upload failed")

const photo = payload.data[0]
console.log(photo.shortUrl, photo.publicImageUrl)

Don't set the Content-Type header yourself: FormData adds the correct multipart boundary automatically.

3. The response and its three URLs

{
  "data": [{
    "id": "…",
    "shortUrl": "Ab3dE7xK",
    "shareUrl": "https://anonshot.com/p/Ab3dE7xK",
    "publicImageUrl": "https://anonshot.com/p/Ab3dE7xK/image",
    "fileUrl": "https://anonshot.com/api/v1/photos/Ab3dE7xK/file",
    "mime": "image/webp",
    "size": 183421,
    "width": 2048,
    "height": 1365,
    "burnAfterRead": false,
    "passwordProtected": false
  }]
}
ValueUse it forAccess
shareUrlOpening the AnonShot page with its interfacePublic
publicImageUrlLLMs, <img>, direct downloadPublic, no HTML
fileUrlDownloading the same image without exposing it publiclyBearer + photos:read

The direct address is always /p/{code}/image. It returns the image bytes as WebP at their processed size, not an HTML page. Photos created with an account token don't expire, unless deleted or opened once as a one-time share. A password-protected photo won't work for an LLM that doesn't have the unlock cookie.

4. Hand the image to an LLM

If the provider accepts an image URL, pass it photo.publicImageUrl directly. If the image must not be public, fetch the bytes from fileUrl on your server instead, then send those bytes to the provider according to its API.

const imageResponse = await fetch(photo.fileUrl, {
  headers: { Authorization: `Bearer ${process.env.ANONSHOT_TOKEN}` },
})

if (!imageResponse.ok) throw new Error("Image not reachable")
const imageBytes = await imageResponse.arrayBuffer()
// Then pass imageBytes to the LLM SDK.

Never pass the AnonShot token to the LLM and never add it as a query parameter. The token is only used between your backend and anonshot.com.

5. Read, list and delete

# List your photos
curl https://anonshot.com/api/v1/photos \
  -H "Authorization: Bearer $ANONSHOT_TOKEN"

# Read a photo's details
curl https://anonshot.com/api/v1/photos/Ab3dE7xK \
  -H "Authorization: Bearer $ANONSHOT_TOKEN"

# Download its cleaned WebP
curl https://anonshot.com/api/v1/photos/Ab3dE7xK/file \
  -H "Authorization: Bearer $ANONSHOT_TOKEN" \
  --output Ab3dE7xK.webp

# Permanently delete the share
curl -X DELETE https://anonshot.com/api/v1/photos/Ab3dE7xK \
  -H "Authorization: Bearer $ANONSHOT_TOKEN"

Strip EXIF metadata only

This route takes an image and returns a cleaned WebP directly, without creating a share link.

curl -X POST https://anonshot.com/api/v1/exif/clean \
  -H "Authorization: Bearer $ANONSHOT_TOKEN" \
  -F "files=@photo.jpg" \
  --output photo-without-metadata.webp

The X-AnonShot-Photo-Id header holds the operation ID. JPEG, PNG and WebP are accepted on this route.

All routes

RoutePurpose
GET /api/v1API discovery, no authentication
GET · POST /api/v1/photosList photos or upload up to 20 images
GET · PATCH · DELETE /api/v1/photos/{code}Read, edit or delete a share
GET /api/v1/photos/{code}/fileGet the cleaned WebP with a token
GET /p/{code}/imageServe a public image directly, no HTML page
POST /api/v1/exif/cleanClean an image and get the WebP back
POST /api/v1/uploads/presignPrepare a direct upload to storage
GET · POST /api/v1/albumsList or create albums
GET · PATCH · DELETE /api/v1/albums/{code}Read, edit or delete an album
POST · DELETE /api/v1/albums/{code}/photosAdd or remove photos

Responses, errors and quotas

Successful JSON responses use {"data": …}. Errors use {"error":{"code":"…","message":"…"}}. Handle in particular 400 (request or image), 401 (token), 403 (scope) and 429 (quota). The X-RateLimit-Remaining and X-Monthly-Remaining headers show the remaining quotas.

Create or revoke an API token
Image hosting & EXIF removal API — AnonShot