v1

Warpbin API

Warpbin brings an event's photos and videos together in one gallery. Guests open a link or scan a QR code and upload from their phone; the host manages everything from the gallery's Manage page. The API lets your software take part too: event apps and photographers use it to create galleries, upload into them, sort files into albums, and keep track of what's there, how often it's shared and downloaded, and what to delete.

How it works

Four steps from nothing to your first photo in a gallery:

Building with an AI assistant?

Paste this prompt into it. It sends your assistant to a plain-text copy of these docs, written for language models.

I'm building with the Warpbin API. Read its reference at https://warpbin.com/docs/api/llms.txt and use only the endpoints, fields and errors it documents.

Assistant can't open links? Open llms.txt and paste the whole file instead.

How it fits together

Your key acts for your account. Whatever you do through the API, the host sees in Manage, and the other way round: it's one gallery, not a copy.

Your account · The API key acts for it

Gallery · One event: a guest link, and a Manage page for the host

Album · Optional groups, such as one per part of the event

File · A photo, video or other file, known by its filename

At a glance

Base URLhttps://warpbin.com/api/v1
AuthYour key in an X-API-Key header (Authentication)
FormatJSON in and out; files go up as uploads and come down as bytes
IdsA gallery's id, an album's album_id, and a file's filename (Concepts)

Quick start

From nothing to your first photo in a gallery. The two calls run on their own, so you can paste them one at a time.

  1. 1

    Get an API key

    Email contact@warpbin.com for a key. It starts with wb_live_ and acts for your Warpbin account; send it with every request (Authentication).

  2. 2

    Get a gallery

    Buy a gallery, one-time or Pro. Its id is the part after /manage/ in its Manage link (a custom guest link isn't the id).

    Any gallery takes uploads. To list, delete and sort its files into albums, the gallery must be on the Pro subscription of the account your key belongs to; with Pro you can also create galleries through the API.

  3. 3

    Upload photos and videos

    Send each file to the gallery. The answer has the name it was stored under, which is the file's id from now on. For videos, big files or phone networks, use a resumable upload: it survives dropped connections and never stores a file twice.

    Request
    import requests
    with open('photo.jpg', 'rb') as f:
    response = requests.post(
    'https://warpbin.com/api/v1/uploadFile',
    headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
    files={'file_contents': f},
    data={
    'filename': 'photo.jpg',
    'drive_id': 'abc123xyz',
    # 'album_id': '…', to put it in an album
    },
    )
    print(response.json()['filename'])
    Response · 202 Accepted
    {
    "message": "File upload accepted for processing.",
    "filename": "photo_2.jpg",
    "file_preview_url": "https://warpbin.com/file?id=abc123xyz&file=photo_2.jpg",
    "file_download_url": "https://warpbin.com/api/v1/getFile?id=abc123xyz&file=photo_2.jpg"
    }
  4. 4

    See them arrive

    Open the gallery link: the photo is there for guests, and in Manage for the host. It shows as processing for a few seconds while its previews are made, then ready. On a Pro gallery you can list the files yourself:

    Request
    import requests
    page = requests.get(
    'https://warpbin.com/api/v1/galleries/abc123xyz/files',
    headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
    params={'limit': 100}, # add 'album_id' for one album
    ).json()
    files, next_after = page['files'], page['next_after']
    Response · 200 OK
    {
    "files": [
    {
    "filename": "IMG_0001.jpg",
    "album_id": "3f2b8c1e-5d4a-4f7b-9e2c-1a6d0b7e91c4",
    "type": "image",
    "size": 2483112,
    "status": "ready",
    "capture_time": "2026-10-26T14:05:40Z",
    "uploaded_at": "2026-10-26T14:05:52Z",
    "shares": 3,
    "downloads": 1
    }
    ],
    "next_after": null
    }

Next: read Concepts for how galleries, albums and files fit together, or browse the API reference.

Authentication

Send your API key with every request, in either of these headers. The key acts for your account: it can manage the galleries your account owns.

X-API-Keyheader

The key as it is: wb_live_xxx

Authorizationheader

As a bearer token: Bearer wb_live_xxx

Errors
401

No key sent

{
  "error": "API key required",
  "type": "MISSING_API_KEY"
}
401

The key isn't valid

{
  "error": "Invalid API key",
  "type": "INVALID_API_KEY"
}

Galleries, albums and files

Three things, each inside the one before. Every call names the gallery it works on, and most name an album or a file in it.

Galleries

One event's photos and videos, with a link for guests and a Manage page for the host. Its id is in the Manage link, warpbin.com/manage/{gallery_id}. Galleries used to be called drives, so some endpoints name the id drive_id or id; it's the same value.

Your key can upload into any gallery that takes uploads. Listing, deleting and albums work on the galleries of your account's Pro subscription; others answer NOT_GALLERY_OWNER.

admin_idThe gallery's admin password (the pd in its Manage link). You only send it to upload into a gallery whose host turned guest uploads off.

Albums

Optional groups inside a gallery, such as one per part of the event. A file is in one album or none. Album names are unique in a gallery, and an album keeps its album_id when the host renames it, so store the id rather than the name. Uploading never creates an album: create it first.

Files

A file's id is its filename: the name it was stored under, which the upload tells you. It never changes. If the gallery already has a file by that name, the new one gets a free name (photo_2.jpg).

statusprocessing while its previews are made, then ready, or failed when the file is stored but no preview could be made
Hidden filesFiles the host hid from guests are still listed, and you can manage them like any other
PhotosJPEG, PNG, GIF, WebP, HEIC, AVIF
VideosMP4, MOV, AVI, WebM, MKV
Other filesPDF, TXT and others, kept as they are

Errors and retries

A request that fails answers with an HTTP status and a JSON body: error says what went wrong in plain words, and type is a stable code to branch on. Each endpoint lists its own errors; these are shared.

{
"error": "No gallery with this id.",
"type": "GALLERY_NOT_FOUND"
}

Any request

401

Missing or invalid API key

{
  "error": "API key required",
  "type": "MISSING_API_KEY"
}
429

Too many requests: wait and retry

Too Many Requests
500

Something failed on our side: safe to retry. The type varies by route; branch on the status.

{
  "error": "Server error, try again",
  "type": "SERVER_ERROR"
}

Any /v1/galleries/… request

404

No gallery with that id

{
  "error": "No gallery with this id.",
  "type": "GALLERY_NOT_FOUND"
}
403

The gallery belongs to another account

{
  "error": "This gallery belongs to a different account than this API key.",
  "type": "NOT_GALLERY_OWNER"
}
403

The gallery has expired (GALLERY_EXPIRED) or been deleted (GALLERY_DELETED): it can't change any more

{
  "error": "Gallery has expired",
  "type": "GALLERY_EXPIRED"
}
Retrying is safe. Creating an album that exists returns it, deleting something already gone succeeds, and a resumable upload that already finished is never stored twice. After a timeout or a 5xx, send the same request again.

Paging

Lists come a page at a time, ordered by name. Ask for the next page with after, the next_after of the page before; it's null on the last page. Because pages continue from a name rather than a position, deleting items while you page never makes you skip one.

limitinteger

Items per page, 1 to 1000. Default 100.

afterstring

The next_after you were given. Leave it out for the first page.

Rate limits

Requests are rate limited per client to keep the service steady. Past the limit you get 429 Too Many Requests; wait a moment and retry. Resumable uploads have their own, higher limit, so sending chunks never competes with your other calls. Normal use never hits either; email contact@warpbin.com if you think you're being limited by mistake.

API reference

Endpoints

Every endpoint, in the order an integration uses them: the gallery, its albums, uploads, then the files. Each lists its parameters, its answer and the errors only it returns; errors any request can get are in Errors and retries.

Create a gallery

POST/v1/createGallery

Create a gallery owned by your account. The answer has its id and admin password, ready to use. The older /v1/createDrive does the same.

Needs an active Pro subscription on the account the key belongs to.

Body

alt_tenant_urlstring

A custom address for the gallery, as in warpbin.com/brazil-trip

Response

drive_idThe gallery's id
admin_idIts admin password (the pd in its Manage link)
drive_urlThe guest link
manage_urlThe host's Manage page
Keep the admin_id safe: anyone with it can manage the gallery. Every gallery you own, with its Manage link, is also on your dashboard.
Errors
403

No active Pro subscription

{
  "success": false,
  "message": "Active Pro subscription required"
}
400

That custom address isn't allowed (its message says why)

{
  "success": false,
  "message": "…"
}
409

That custom address is taken

{
  "success": false,
  "message": "This alternative URL is already in use by another gallery"
}
Request
import requests
response = requests.post(
'https://warpbin.com/api/v1/createGallery',
headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
json={'alt_tenant_url': 'my-project'},
)
gallery = response.json()
print(gallery['drive_id'], gallery['drive_url'])
Response · 200 OK
{
"success": true,
"drive_id": "abc123xyz",
"admin_id": "UxCIkRbY",
"drive_url": "https://warpbin.com/abc123xyz",
"manage_url": "https://warpbin.com/manage/abc123xyz?pd=UxCIkRbY",
"message": "Gallery created successfully"
}

Create an album

POST/v1/galleries/{gallery_id}/albums

Albums group a gallery's files, for example by part of the event. Names are unique in a gallery, so creating a name that exists returns that album with 200 instead of a new one with 201: a retried create never makes a second album. Keep the album_id: it stays the same if the host renames the album in Manage.

Body

namestringrequired

Up to 255 characters, without commas, semicolons or line breaks

Response

album_idIts id, for uploads and the other album calls
nameIts name
file_countFiles in it, still-processing ones included
created_atWhen it was created
Errors
400

Name not allowed

{
  "error": "Album names cannot be empty",
  "type": "INVALID_ALBUM_NAME"
}
403

The gallery belongs to a different account than this key

{
  "error": "This gallery belongs to a different account than this API key.",
  "type": "NOT_GALLERY_OWNER"
}
Request
import requests
response = requests.post(
'https://warpbin.com/api/v1/galleries/abc123xyz/albums',
headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
json={'name': 'Reception'},
)
album = response.json() # 201 new, 200 already there
print(album['album_id'])
Response · 201 Created
{
"album_id": "3f2b8c1e-5d4a-4f7b-9e2c-1a6d0b7e91c4",
"name": "Reception",
"file_count": 48,
"created_at": "2026-10-26T14:03:11Z"
}

List albums

GET/v1/galleries/{gallery_id}/albums

The gallery's albums by name, a page at a time (see Paging).

Query

limitinteger

Albums per page, 1 to 1000. Default 100.

afterstring

The next_after of the page before

Response

albumsAlbums, each as Create an album returns it
next_afterThe after for the next page, or null on the last
Errors
403

The gallery belongs to a different account than this key

{
  "error": "This gallery belongs to a different account than this API key.",
  "type": "NOT_GALLERY_OWNER"
}
Request
import requests
page = requests.get(
'https://warpbin.com/api/v1/galleries/abc123xyz/albums',
headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
params={'limit': 100},
).json()
albums, next_after = page['albums'], page['next_after']
Response · 200 OK
{
"albums": [
{
"album_id": "3f2b8c1e-5d4a-4f7b-9e2c-1a6d0b7e91c4",
"name": "Reception",
"file_count": 48,
"created_at": "2026-10-26T14:03:11Z"
}
],
"next_after": null
}

Get an album

GET/v1/galleries/{gallery_id}/albums/{album_id}

One album, with how many files are in it now.

Errors
400

album_id isn't an id

{
  "error": "album_id 'day-1' is not a valid id.",
  "type": "INVALID_ALBUM_ID"
}
404

No album with that id in this gallery

{
  "error": "No album with this id in this gallery.",
  "type": "ALBUM_NOT_FOUND"
}
403

The gallery belongs to a different account than this key

{
  "error": "This gallery belongs to a different account than this API key.",
  "type": "NOT_GALLERY_OWNER"
}
Request
import requests
album = requests.get(
'https://warpbin.com/api/v1/galleries/abc123xyz/albums/3f2b8c1e-5d4a-4f7b-9e2c-1a6d0b7e91c4',
headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
).json()
print(album['file_count'])
Response · 200 OK
{
"album_id": "3f2b8c1e-5d4a-4f7b-9e2c-1a6d0b7e91c4",
"name": "Reception",
"file_count": 48,
"created_at": "2026-10-26T14:03:11Z"
}

Delete an album

DELETE/v1/galleries/{gallery_id}/albums/{album_id}

Delete an album. Its photos and videos stay in the gallery, in no album, unless you ask for them to go too. Deleting an album that's already gone succeeds, with no files deleted.

Query

delete_filesboolean

true deletes every photo and video in the album as well, originals and previews. Default false.

Response

deleted_filesThe filenames it deleted (empty without delete_files)
Errors
503

Storage kept some of its files, so the album stays: send the same delete again

{
  "error": "2 of its files couldn't be deleted from storage, so the album was kept. Try again.",
  "type": "STORAGE_ERROR"
}
400

album_id isn't an id

{
  "error": "album_id 'day-1' is not a valid id.",
  "type": "INVALID_ALBUM_ID"
}
403

The gallery belongs to a different account than this key

{
  "error": "This gallery belongs to a different account than this API key.",
  "type": "NOT_GALLERY_OWNER"
}
Request
import requests
response = requests.delete(
'https://warpbin.com/api/v1/galleries/abc123xyz/albums/3f2b8c1e-5d4a-4f7b-9e2c-1a6d0b7e91c4',
headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
params={'delete_files': 'true'},
)
print(response.json()['deleted_files'])
Response · 200 OK
{
"deleted_files": ["IMG_0001.jpg", "IMG_0002.jpg"]
}

Resumable upload

POST/v1/tus
PATCH/v1/tus/{upload_id}
HEAD/v1/tus/{upload_id}
DELETE/v1/tus/{upload_id}

The recommended way to upload: files go in chunks and pick up where they stopped after a dropped connection, using the open TUS 1.0 protocol (creation and termination extensions). Any TUS client library works: point it at /v1/tus, add your API key header and set the metadata below.

How it goes: POST opens a session and returns its address in Location. PATCH sends the bytes in order. After an interruption, HEAD tells you the Upload-Offset to continue from. The upload is done when the offset reaches Upload-Length; DELETE abandons a session you won't finish.

Upload-Metadata

Sent with the POST as the standard TUS Upload-Metadata header (comma-separated key base64(value) pairs, which TUS libraries encode for you).

drive_idstringrequired

The gallery to upload into

filenamestringrequired

The file's name, with its extension. If the gallery already has that name, the file is stored under a free one (video_2.mp4); a finished upload tells you which.

session_idstringrequired

A UUID you make, grouping the uploads of one client session

album_idstring

The album to put it in. Checked before any bytes are sent: an album that doesn't exist refuses the upload at once. Albums are never made by uploading; create them first.

admin_idstring

The gallery's admin password. Needed only when the gallery has guest uploads turned off.

user_idstring

Your own id for who uploaded it, if you want to keep one

consent_givenboolean

The uploader agreed to the gallery's terms (default false)

When it's done

The last PATCH answers 204 and the file is processed in the background (previews, video playback). It shows in List files straight away with status: processing, then ready.

Lost the last response? A finished upload stays at its session address. HEAD answers 200 with Upload-Offset equal to Upload-Length and an Upload-Metadata header holding the stored filename. Sending the last PATCH again answers 204 and writes nothing, so a retry never stores a copy. TUS libraries already treat this as success.

Limits

File sizeUp to 50 GB (Tus-Max-Size on OPTIONS /v1/tus)
Chunk sizeAny size but empty. 1 to 10 MiB suits most networks; go smaller on poor ones.
Unfinished sessionsKept up to 24 hours, and lost on a server restart. A 404 or 410 on a session you know means nothing was stored: start that file again with a new POST.
Errors
400

Required metadata missing

{
  "error": "Missing required field: session_id",
  "type": "MISSING_FIELD"
}
400

An album name was sent (album or album_name): create the album, then send its album_id

{
  "error": "Uploads take album_id, not an album name. Create the album first and send its id.",
  "type": "ALBUM_NAME_NOT_SUPPORTED"
}
400

album_id isn't an id

{
  "error": "album_id 'day-1' is not a valid id.",
  "type": "INVALID_ALBUM_ID"
}
400

No album with that album_id in this gallery (it may have been deleted)

{
  "error": "No album 3f2b…e91c in this gallery. It may have been deleted.",
  "type": "ALBUM_NOT_FOUND"
}
403

The gallery has expired. Don't retry.

{
  "error": "This gallery has expired. Uploads are not allowed.",
  "type": "DRIVE_EXPIRED"
}
403

Guest uploads are off and no admin_id was sent. Don't retry.

{
  "error": "Uploads are disabled for this gallery. Admin ID required.",
  "type": "UPLOADS_DISABLED"
}
403

The plan is full. Don't retry.

{
  "error": "Upload not allowed",
  "type": "PLAN_LIMIT_EXCEEDED"
}
404

Unknown or lost session: start again with a new POST

{
  "error": "Upload not found"
}
409

Offset mismatch: ask HEAD for the offset, then continue from server_offset

{
  "error": "Offset mismatch - client and server out of sync",
  "type": "OFFSET_MISMATCH",
  "server_offset": 106954752,
  "client_offset": 107003904
}
410

Session expired: start again with a new POST

{
  "error": "Upload has expired"
}
413

File too large

{
  "error": "File size exceeds maximum allowed size",
  "type": "FILE_TOO_LARGE",
  "max_size": 53687091200
}
507

Server storage is briefly full: wait and retry

{
  "error": "Server storage critical. Uploads temporarily disabled to prevent system crash.",
  "type": "DISK_SPACE_CRITICAL"
}
Request
# pip install tuspy
import uuid
from tusclient import client
tus_client = client.TusClient(
'https://warpbin.com/api/v1/tus',
headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
)
uploader = tus_client.uploader(
'video.mp4',
chunk_size=5 * 1024 * 1024,
metadata={
'drive_id': 'abc123xyz',
'filename': 'video.mp4',
'session_id': str(uuid.uuid4()),
'album_id': '3f2b8c1e-5d4a-4f7b-9e2c-1a6d0b7e91c4', # optional
},
)
# Sends chunks and resumes from the server-confirmed
# offset after an interruption
uploader.upload()
Response headers
# POST /v1/tus: session created
HTTP/1.1 201 Created
Tus-Resumable: 1.0.0
Location: /api/v1/tus/3f2a9c1e-...
# PATCH /v1/tus/{upload_id}: chunk accepted
HTTP/1.1 204 No Content
Tus-Resumable: 1.0.0
Upload-Offset: 5242880
# HEAD /v1/tus/{upload_id}: where to resume
HTTP/1.1 200 OK
Tus-Resumable: 1.0.0
Upload-Offset: 106954752
Upload-Length: 296201395
# HEAD /v1/tus/{upload_id}: upload already finished
# (Upload-Metadata carries the stored filename, here "video_2.mp4")
HTTP/1.1 200 OK
Tus-Resumable: 1.0.0
Upload-Offset: 296201395
Upload-Length: 296201395
Upload-Metadata: filename dmlkZW9fMi5tcDQ=

Simple upload

POST/v1/uploadFile

One request per file, as a multipart form. Fine for small photos on a steady connection; use a resumable upload for videos, big files or mobile networks. A retry after a lost response stores the file again under a new name (photo_2.jpg), which a resumable upload never does.

Form fields

file_contentsfilerequired

The file

filenamestringrequired

Its name, with its extension

drive_idstringrequired

The gallery to upload into

album_idstring

The album to put it in. An album that doesn't exist refuses the upload.

admin_idstring

The gallery's admin password. Needed only when the gallery has guest uploads turned off.

user_idstring

Your own id for who uploaded it, if you want to keep one

consent_givenboolean

The uploader agreed to the gallery's terms (default false)

Response

filenameThe name it was stored under: the file's id
file_preview_urlIts page in the gallery (opens without a key)
file_download_urlThe original, through Download a file

The answer is 202 Accepted: the file is processed in the background, so its page shows it a few seconds to minutes later, depending on its size.

Errors
400

A required field is missing

{
  "error": "Missing required field: drive_id",
  "type": "MISSING_FIELD"
}
400

No gallery with that drive_id

{
  "error": "Invalid gallery ID",
  "type": "INVALID_DRIVE_ID"
}
400

An album name was sent (album or album_name): create the album, then send its album_id

{
  "error": "Uploads take album_id, not an album name. Create the album first and send its id.",
  "type": "ALBUM_NAME_NOT_SUPPORTED"
}
400

album_id isn't an id

{
  "error": "album_id 'day-1' is not a valid id.",
  "type": "INVALID_ALBUM_ID"
}
400

No album with that album_id in this gallery (it may have been deleted)

{
  "error": "No album 3f2b…e91c in this gallery. It may have been deleted.",
  "type": "ALBUM_NOT_FOUND"
}
403

The gallery has expired

{
  "error": "This gallery has expired. Uploads are not allowed.",
  "type": "DRIVE_EXPIRED"
}
403

Guest uploads are off and no admin_id was sent

{
  "error": "Uploads are disabled for this gallery. Admin ID required.",
  "type": "UPLOADS_DISABLED"
}
507

The gallery's storage is full

{
  "error": "Uploading this file would exceed the gallery's storage limit.",
  "type": "STORAGE_SPACE"
}
Request
import requests
with open('photo.jpg', 'rb') as f:
response = requests.post(
'https://warpbin.com/api/v1/uploadFile',
headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
files={'file_contents': f},
data={
'filename': 'photo.jpg',
'drive_id': 'abc123xyz',
# 'album_id': '…', to put it in an album
},
)
print(response.json()['filename'])
Response · 202 Accepted
{
"message": "File upload accepted for processing.",
"filename": "photo_2.jpg",
"file_preview_url": "https://warpbin.com/file?id=abc123xyz&file=photo_2.jpg",
"file_download_url": "https://warpbin.com/api/v1/getFile?id=abc123xyz&file=photo_2.jpg"
}

List files

GET/v1/galleries/{gallery_id}/files

Every photo, video and other file in the gallery, or in one album, by name a page at a time (see Paging). Hidden and still-processing files are included. A file that was deleted is simply no longer listed.

Query

album_idstring

Only this album's files

limitinteger

Files per page, 1 to 1000. Default 100.

afterstring

The next_after of the page before

Each file

filenameIts id
album_idIts album, or null
typeimage, video or other
sizeBytes
statusprocessing (being prepared), ready, or failed (stored, but its preview couldn't be made)
capture_timeWhen it was taken, from the photo's own data, or null
uploaded_atWhen it arrived
sharesTimes guests shared it: the photo itself, its link, or a copied link
downloadsTimes it was downloaded on its own (not as part of a whole-gallery zip), counted since October 2026. Downloads through this API's getFile count too.
Errors
400

album_id isn't an id

{
  "error": "album_id 'day-1' is not a valid id.",
  "type": "INVALID_ALBUM_ID"
}
403

The gallery belongs to a different account than this key

{
  "error": "This gallery belongs to a different account than this API key.",
  "type": "NOT_GALLERY_OWNER"
}
Request
import requests
page = requests.get(
'https://warpbin.com/api/v1/galleries/abc123xyz/files',
headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
params={'limit': 100}, # add 'album_id' for one album
).json()
files, next_after = page['files'], page['next_after']
Response · 200 OK
{
"files": [
{
"filename": "IMG_0001.jpg",
"album_id": "3f2b8c1e-5d4a-4f7b-9e2c-1a6d0b7e91c4",
"type": "image",
"size": 2483112,
"status": "ready",
"capture_time": "2026-10-26T14:05:40Z",
"uploaded_at": "2026-10-26T14:05:52Z",
"shares": 3,
"downloads": 1
}
],
"next_after": null
}

Get file links

GET/v1/fileInfo?drive_id={drive_id}&filename={filename}

A file's page and download links, for example to show or send once an upload is done. Your own galleries answer for every file; another gallery answers only for files its guests can see.

Query

drive_idstringrequired

The gallery

filenamestringrequired

The file

Response

filenameThe file
file_preview_urlIts page in the gallery (opens without a key)
file_download_urlThe original, through Download a file
Errors
400

A parameter is missing

{
  "error": "Missing 'filename' parameter",
  "type": "MISSING_PARAMETER"
}
400

No gallery with that drive_id

{
  "error": "Invalid gallery ID",
  "type": "INVALID_DRIVE_ID"
}
404

No such file, or one this key may not see

{
  "error": "File not found",
  "type": "NOT_FOUND"
}
Request
import requests
info = requests.get(
'https://warpbin.com/api/v1/fileInfo',
headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
params={'drive_id': 'abc123xyz', 'filename': 'photo.jpg'},
).json()
print(info['file_preview_url'])
Response · 200 OK
{
"filename": "photo.jpg",
"file_preview_url": "https://warpbin.com/file?id=abc123xyz&file=photo.jpg",
"file_download_url": "https://warpbin.com/api/v1/getFile?id=abc123xyz&file=photo.jpg"
}

Download a file

GET/v1/getFile?id={drive_id}&file={filename}

The original file, as uploaded, with its content type. Your own galleries answer for every file; another gallery follows its host's rules (hidden files and turned-off downloads stay closed). Note the short names: id is the gallery, file the filename.

Query

idstringrequired

The gallery (its drive_id)

filestringrequired

The file (its filename)

Errors
400

A parameter is missing

{
  "error": "Missing 'file' parameter",
  "type": "MISSING_PARAMETER"
}
400

Not a filename

{
  "error": "Invalid filename format",
  "type": "INVALID_FILENAME"
}
404

No such file, or one this key may not download

{
  "error": "File not found",
  "type": "NOT_FOUND"
}
502

Storage didn't answer: retry

{
  "error": "Storage is unavailable, try again",
  "type": "STORAGE_ERROR"
}
Request
import requests
response = requests.get(
'https://warpbin.com/api/v1/getFile',
headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
params={'id': 'abc123xyz', 'file': 'photo.jpg'},
)
with open('photo.jpg', 'wb') as f:
f.write(response.content)

Delete a file

DELETE/v1/galleries/{gallery_id}/files/{filename}

Delete a file for good: its original and previews go at once, from guests' screens too. Answers 204 whether the file was there or already gone, so it's safe to repeat. A file still being processed can be deleted too. Encode the filename for the path.

Copies people already downloaded or shared to other apps can't be called back, and link previews in chat apps may show the old picture for up to a day.
Errors
503

Storage kept the file: send the same delete again

{
  "error": "Storage wouldn't delete the file. Try again.",
  "type": "STORAGE_ERROR"
}
403

The gallery belongs to a different account than this key

{
  "error": "This gallery belongs to a different account than this API key.",
  "type": "NOT_GALLERY_OWNER"
}
Request
import requests
from urllib.parse import quote
response = requests.delete(
'https://warpbin.com/api/v1/galleries/abc123xyz/files/' + quote('IMG_0001.jpg'),
headers={'X-API-Key': 'wb_live_xxxxxxxxxxxx'},
)
print(response.status_code) # 204