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
- Create a token in your account settings.
- Send the file to
POST /api/v1/photoswith that token. - Keep the
shortUrl, or simply the ready-made URLs returned in the response. - Give
publicImageUrlto the LLM if the image is public, or downloadfileUrlwith 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_TOKENphotos:writeallows uploading, editing and deleting.photos:readallows 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
}]
}| Value | Use it for | Access |
|---|---|---|
| shareUrl | Opening the AnonShot page with its interface | Public |
| publicImageUrl | LLMs, <img>, direct download | Public, no HTML |
| fileUrl | Downloading the same image without exposing it publicly | Bearer + 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.webpThe X-AnonShot-Photo-Id header holds the operation ID. JPEG, PNG and WebP are accepted on this route.
All routes
| Route | Purpose |
|---|---|
| GET /api/v1 | API discovery, no authentication |
| GET · POST /api/v1/photos | List 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}/file | Get the cleaned WebP with a token |
| GET /p/{code}/image | Serve a public image directly, no HTML page |
| POST /api/v1/exif/clean | Clean an image and get the WebP back |
| POST /api/v1/uploads/presign | Prepare a direct upload to storage |
| GET · POST /api/v1/albums | List or create albums |
| GET · PATCH · DELETE /api/v1/albums/{code} | Read, edit or delete an album |
| POST · DELETE /api/v1/albums/{code}/photos | Add 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.