Send an image to Snipbud from your own code and get a clean, shareable link back. One request per upload. No SDK required.
This is a plain HTTP (REST) API that speaks JSON. If you can make an HTTP request, whether with curl, Node.js, Python, or anything else, you can use it. The examples below use curl and Node.js, but the shape is the same everywhere.
On this page
Quick start Getting a key Authentication Workspaces Upload an image List images Get one Delete Rate limits Errors Keeping keys safeThree steps to your first shared link:
sk_live_).url from the response, that's your shareable link.# upload photo.png and get a public link back
curl -X POST "https://snipbud.apoiolabs.com/api/v1/images?visibility=public" \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: image/png" \
--data-binary @photo.png
An API key is a long secret string that proves a request is really coming from you, instead of logging in with an email and password each time.
You can create several keys (up to 25 active at a time), see when each was last used, and revoke any of them instantly if one leaks. Revoking takes effect immediately.
Every request must include your key in a header called Authorization, in this exact format (the word Bearer, a space, then your key):
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
A few important rules:
The base URL for every endpoint is:
https://snipbud.apoiolabs.com/api/v1
A key always belongs to the workspace it was created in, and everything it does happens there:
visibility option is ignored for them.You can't change a key's workspace later, create a new key in the workspace you want instead.
POST /api/v1/images
Put the raw image file as the request body and set the Content-Type header to the file's format. Content-Type is just a label that tells us what kind of file you're sending. There's no form, no multipart, no presign step, one request does it.
| Name | Description | |
|---|---|---|
| visibility | optional | public (anyone with the link can view) or private (only you). Defaults to private. Ignored for team keys, whose uploads are always team-visible. |
Supported types: image/png, image/jpeg, image/gif, image/webp, image/avif, and image/svg+xml. Up to 100 MB per request.
image/png (or send something that isn't an image at all), the request is rejected with 400. Set the header to the file's true format.With curl (--data-binary @file sends the file's raw bytes):
curl -X POST "https://snipbud.apoiolabs.com/api/v1/images?visibility=public" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: image/png" \
--data-binary @photo.png
With Node.js (built-in fetch, Node 18+):
import { readFile } from "node:fs/promises";
const bytes = await readFile("photo.png");
const res = await fetch("https://snipbud.apoiolabs.com/api/v1/images?visibility=public", {
method: "POST",
headers: {
"Authorization": "Bearer sk_live_...",
"Content-Type": "image/png",
},
body: bytes,
});
const data = await res.json();
console.log(data.url); // https://snipbud.apoiolabs.com/i/aX9f2Qk1bZ.png
201 Created{
"key": "aX9f2Qk1bZ.png",
"url": "https://snipbud.apoiolabs.com/i/aX9f2Qk1bZ.png",
"visibility": "public",
"size": 48213,
"contentType": "image/png"
}
| Field | What it is |
|---|---|
| key | The image's unique id, including its file extension. You'll use this to fetch or delete it later. |
| url | The shareable link. Paste it anywhere. |
| visibility | public, private, or team. |
| size | Stored size in bytes. |
| contentType | The image type we stored. |
GET /api/v1/images
Returns the images in the key's workspace, newest first.
| Name | Description | |
|---|---|---|
| limit | optional | How many to return, 1 to 100. Default 50. |
| before | optional | For paging. Pass the nextBefore value from the previous response to get the next page. |
curl "https://snipbud.apoiolabs.com/api/v1/images?limit=50" \
-H "Authorization: Bearer sk_live_..."
200 OK{
"images": [
{
"key": "aX9f2Qk1bZ.png",
"url": "https://snipbud.apoiolabs.com/i/aX9f2Qk1bZ.png",
"visibility": "public",
"contentType": "image/png",
"size": 48213,
"createdAt": 1723852800000,
"modifiedAt": 1723852800000
}
],
"nextBefore": 1723852800000
}
Paging: when nextBefore is a number, there may be more images, call again with ?before=<that number>. When it's null, you've reached the end.
createdAt and modifiedAt are Unix time in milliseconds (milliseconds since 1 Jan 1970). In JavaScript, new Date(createdAt) turns one into a readable date.GET /api/v1/images/{key}
Returns the details of a single image (the same fields as a list entry). Responds 404 if the key doesn't exist in this workspace.
curl "https://snipbud.apoiolabs.com/api/v1/images/aX9f2Qk1bZ.png" \
-H "Authorization: Bearer sk_live_..."
DELETE /api/v1/images/{key}
Removes the image and breaks its link. This can't be undone. In a team, you can delete your own uploads; team admins and owners can delete anyone's.
curl -X DELETE "https://snipbud.apoiolabs.com/api/v1/images/aX9f2Qk1bZ.png" \
-H "Authorization: Bearer sk_live_..."
Returns { "ok": true, "key": "…" } on success.
To keep the service fair and safe, requests are capped. When you hit a cap you get a 429 response with a Retry-After header telling you how many seconds to wait. There are three limits:
| Limit | What it means |
|---|---|
| Uploads | Up to 300 uploads per minute on Max. Plenty for normal use. |
| Per key | Up to 600 requests per minute for each key, across all endpoints. This stops one key from overwhelming the service. |
| Failed logins | If many requests from one network use a bad or missing key, that network is briefly blocked. This never affects a working key. |
Good practice: when you see a 429, wait the number of seconds in Retry-After before trying again, rather than retrying immediately.
Every error is JSON with a human-readable error message and a standard HTTP status code:
{ "error": "Invalid or revoked API key." }
| Status | Meaning & what to do |
|---|---|
400 | Bad request, an unsupported image type, an empty body, or bytes that don't match the Content-Type you set. Check the file and header. |
401 | Your key is missing, malformed, revoked, or expired. Check the Authorization header, or create a new key. |
402 | This workspace isn't on Max (the plan lapsed, or the key predates a downgrade). Upgrade to use the API. |
404 | No image with that key exists in this workspace. (You'll also get 404 for an image that belongs to someone else, we never confirm it exists.) |
413 | The file is too big, over 100 MB, or over your plan's per-file limit. |
429 | You hit a rate limit. Wait for the seconds in the Retry-After header, then retry. |
500 | Something went wrong on our side. Retrying after a short wait is safe. |