X02 API documentation
X02 has two programmatic interfaces:
- MCP server at
https://up.x02.me/mcp, for AI assistants. OAuth sign-in, scoped and revocable. See Use X02 with AI assistants. - REST API at
https://up.x02.me/api, for your own code. Authenticate with anx-api-keyheader; get a key from the dashboard (how). Vision endpoints live athttps://vision.x02.me.
MCP server
X02 runs a remote Model Context Protocol server at https://up.x02.me/mcp (Streamable HTTP, OAuth 2.1). Add it as a connector in Claude, ChatGPT, Gemini, Grok, Le Chat or any other MCP app, sign in to X02 in the browser window that opens, and the assistant can upload, find, view, rename, move, delete, organize and share the account's files. No API key is ever pasted into the AI app.
claude mcp add --transport http x02 https://up.x02.me/mcp
Tools:
get_account— Plan, limits, and how much of the daily upload quota is left.list_files— Page through uploads, narrowing by search, folder, file kind and upload date.get_file— Metadata for one file: URL, size, upload date, kind, folder and whether it is private.view_file— Show the assistant the actual image, so it can identify a file rather than guess from its name.request_file_from_user— Get a link the user drops a file into — the working route for anything attached to a chat.check_file_handoff— Collect what they dropped. Waits for the upload, so nobody has to say “done”.upload_file— Upload small file content directly. Bytes are base64-encoded into the call, so in practice this suits icons and snippets, not screenshots — use request_file_from_user for a file on your machine, or upload_from_url for an image already online.upload_from_url— Rehost a remote image by URL. Images only. No size cap beyond your plan limit.rename_files— Rename one or more stored files. Each hosted URL changes with it.move_files— Move one or many files into a folder, or back to the root library.delete_files— Permanently delete one or more files. Not reversible.list_folders— List folders, including file counts and share state.create_folder— Create a folder.delete_folder— Delete a folder. Not reversible.share_folder— Turn a folder into a shared album and return the link.
Scopes:
account:read— Read your username, plan, limits, and usage.files:read— List and inspect your files.files:write— Upload, rename, and move files.files:delete— Delete files.folders:read— List your folders.folders:write— Create, delete, and share folders.
Core access
Connectivity checks and browser-friendly file removal.
GET /api/ping — Health check
Confirms that the public API is reachable.
Auth: No authentication required
curl "https://up.x02.me/api/ping"
Response (200 OK):
{
"message": "Hello from X02 API!"
}
GET /api/delete/:filename — Delete a file (browser-friendly)
Deletes one hosted file using a GET request, so it works from a browser address bar or bookmark.
Auth: apiKey query parameter required
This route authenticates from the apiKey query parameter (it also accepts the x-api-key header). Anyone with the link can trigger the delete, so treat these URLs as secrets.
Parameters:
filename(path, required) — Stored filename to remove.apiKey(query, required) — The authenticated account key.
curl "https://up.x02.me/api/delete/ABCD.png?apiKey=YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"message": "Image deleted successfully"
}
Uploads
File ingestion for direct integrations and automation.
POST /api/upload — Upload a file
Uploads a file and returns the hosted URL plus the stored filename.
Auth: x-api-key header required for external requests
Anonymous uploads are only accepted for same-origin requests from the X02 website itself. Any request from outside x02.me must send x-api-key or it is rejected with 401. Default response is plain text containing the hosted URL — use ?format=json or the x-response-format: json header when you need structured JSON instead. The response is sent once the file is stored, so the returned URL works immediately. One file per request. Where the file lands: a folder key (fk_...) files it into that key's folder, while your primary key uses the account's default upload folder, falling back to the root library.
Parameters:
file(form-data, required) — The file payload to upload. Use 'file' or 'image' as the field name.expiry(form-data) — Optional expiry value such as 1h, 6h, 1d, 7d, or 30d.format(query) — Optional. Set to json for a JSON response instead of plain text.x-api-key(header) — Required for any request originating outside the X02 website.x-response-format(header) — Optional. Set to json for a JSON response.
curl -X POST "https://up.x02.me/api/upload?format=json" \
-H "x-api-key: YOUR_API_KEY" \
-F "[email protected]" \
-F "expiry=1d"
Response (200 OK):
{
"success": true,
"data": {
"url": "https://x02.me/i/ABCD.png",
"filename": "ABCD.png",
"originalFilename": "image.png",
"mimeType": "image/png",
"sizeBytes": 245678,
"expiry": "1d"
}
}
Errors (JSON: { "success": false, "error": "..." }):
400— Not multipart/form-data, no file, more than one file, a field other than "file" or "image", or a blocked file type.401— No x-api-key on a request from outside the X02 website.403— Invalid or banned key, a folder key without Pro, or the Free plan's storage is full.409— Another upload took the generated name at the same moment. Retry; a new name is picked.413— Larger than the plan limit: 80MB anonymous, 200MB Free, 512MB Pro.429— Rate limit reached. Wait for the number of seconds in retryAfter.502— Storage was briefly unavailable and nothing was saved. Retry the upload.
POST /api/upload/url — Upload from URL
Downloads an image from a remote URL and uploads it to X02.
Auth: x-api-key header required for external requests
The URL must resolve to an image content type (jpeg, png, gif, webp, and similar). Internal and private hostnames are rejected. Size limits match direct uploads: 80MB anonymous, 200MB free, 512MB Pro.
Parameters:
imageUrl(json body, required) — The remote image URL to download and upload.expiry(json body) — Optional expiry value such as 1h, 6h, 1d, 7d, or 30d.source(json body) — Optional source identifier. Defaults to 'url'.x-api-key(header) — Required for any request originating outside the X02 website.
curl -X POST "https://up.x02.me/api/upload/url" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"imageUrl":"https://example.com/photo.jpg","expiry":"1d"}'
Response (200 OK):
{
"success": true,
"data": {
"url": "https://x02.me/i/ABCD12.jpg",
"filename": "ABCD12.jpg",
"originalFilename": "photo.jpg",
"mimeType": "image/jpeg",
"sizeBytes": 245678,
"expiry": "1d"
}
}
Errors (JSON: { "success": false, "error": "..." }):
400— Missing or invalid URL, not http(s), a private address, the remote server returned an error or a non-image type, or the download took over 10 minutes.401— No x-api-key on a request from outside the X02 website.403— Invalid or banned key, a folder key without Pro, or the Free plan's storage is full.409— Another upload took the generated name at the same moment. Retry; a new name is picked.413— The image is larger than the plan limit. Checked while downloading, so a missing or wrong Content-Length does not get around it.429— Rate limit reached. Wait for the number of seconds in retryAfter.502— Storing the image failed and nothing was saved. Retry.
Files and folders
Account-level file management and organization.
GET /api/user/dashboard — Read dashboard data
Returns the account profile, a paginated page of uploads, folders, folder keys, and pagination metadata.
Auth: x-api-key header required
Everything is nested under data. Uploads are returned as data.uploads — there is no top-level images array.
Parameters:
x-api-key(header, required) — The authenticated account key.page(query) — Page number for paginated results. Defaults to 1.limit(query) — Number of results to return. Defaults to 10.search(query) — Filter files by search text.folderId(query) — Limit results to one folder.sortBy(query) — Sort field. Defaults to timestamp.sortOrder(query) — Sort direction, asc or desc. Defaults to desc.
curl "https://up.x02.me/api/user/dashboard?page=1&limit=20" \
-H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": {
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"username": "developer1",
"email": "[email protected]",
"apiKey": "YOUR_API_KEY",
"createdAt": "2026-05-18T10:00:00.000Z",
"plan": "basic",
"limits": {
"dailyLimit": 250,
"monthlyStorageLimit": 5368709120
},
"usage": {
"count": 128,
"lastUsed": "2026-09-07T18:22:10.000Z",
"monthlyStorageUsed": 734003200,
"currentMonth": "2026-09",
"todayCount": 12,
"remaining": 238
},
"connections": {
"discord": { "connected": false, "username": null },
"google": { "connected": false, "username": null }
},
"defaultUploadFolderId": null,
"banInfo": null
},
"uploads": [
{
"filename": "ABCD.png",
"originalName": "screenshot.png",
"url": "https://developer1.x02.me/i/ABCD.png",
"timestamp": "2026-09-07T18:22:10.000Z",
"updatedAt": null,
"size": 245678,
"folderId": null,
"source": "web",
"isPrivate": false
}
],
"folders": [],
"folderKeys": [],
"pagination": {
"currentPage": 1,
"totalPages": 5,
"totalUploads": 96,
"totalUserFiles": 128,
"limit": 20,
"hasNextPage": true,
"hasPrevPage": false
}
}
}
DELETE /api/user/images/:filename — Delete a file
Deletes one hosted file owned by the authenticated account.
Auth: x-api-key header required
Parameters:
filename(path, required) — Stored filename to remove.x-api-key(header, required) — The authenticated account key.
curl -X DELETE "https://up.x02.me/api/user/images/ABCD.png" \
-H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"message": "Image deleted successfully"
}
PUT /api/user/images/:filename/rename — Rename a file
Renames one hosted file and returns the new hosted URL.
Auth: x-api-key header required
The new filename must include an extension. Spaces are converted to hyphens. Changing the extension also updates the stored content type.
Parameters:
filename(path, required) — Stored filename to rename.newFilename(json body, required) — New filename to assign, including extension.x-api-key(header, required) — The authenticated account key.
curl -X PUT "https://up.x02.me/api/user/images/ABCD.png/rename" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"newFilename":"renamed.png"}'
Response (200 OK):
{
"success": true,
"message": "File renamed successfully",
"oldFilename": "ABCD.png",
"newFilename": "renamed.png",
"newUrl": "https://developer1.x02.me/i/renamed.png"
}
PATCH /api/user/images/:filename/replace — Replace a file
Swaps the contents of an existing file while keeping the same filename and hosted URL.
Auth: x-api-key header required
The URL does not change — the stored object is overwritten in place and the CDN cache is purged, so existing links keep working and start serving the new content. Filename, folder, and privacy are all preserved; size, mime type, and originalName are updated. Note the extension is not changed to match the new file, so replacing an .png with a JPEG leaves a .png URL serving JPEG bytes. Returns 413 if the size increase would push you past your storage limit.
Parameters:
filename(path, required) — Stored filename to replace.file(form-data, required) — The replacement file. Use 'file' or 'image' as the field name.x-api-key(header, required) — The authenticated account key.
curl -X PATCH "https://up.x02.me/api/user/images/ABCD.png/replace" \
-H "x-api-key: YOUR_API_KEY" \
-F "[email protected]"
Response (200 OK):
{
"success": true,
"message": "File replaced successfully",
"data": {
"filename": "ABCD.png",
"url": "https://developer1.x02.me/i/ABCD.png",
"mimeType": "image/png",
"sizeBytes": 301244,
"oldSizeBytes": 245678,
"updatedAt": "2026-09-08T12:00:00.000Z"
}
}
PATCH /api/user/images/:filename/move — Move one file
Moves one stored file into a folder or back to the root library.
Auth: x-api-key header required
Parameters:
filename(path, required) — Stored filename to move.folderId(json body) — Target folder ID. Omit or send null to move back to root.x-api-key(header, required) — The authenticated account key.
curl -X PATCH "https://up.x02.me/api/user/images/ABCD.png/move" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"folderId":"YOUR_FOLDER_ID"}'
Response (200 OK):
{
"success": true,
"message": "File moved successfully"
}
PATCH /api/user/images/move — Move multiple files
Moves several stored files in a single request and reports per-file results.
Auth: x-api-key header required
This call is partially successful by design: files you do not own are skipped and listed in errors rather than failing the whole request. Always inspect both arrays.
Parameters:
filenames(json body, required) — Non-empty array of stored filenames to move.folderId(json body) — Target folder ID. Omit or send null to move back to root.x-api-key(header, required) — The authenticated account key.
curl -X PATCH "https://up.x02.me/api/user/images/move" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filenames":["A.png","B.jpg"],"folderId":"YOUR_FOLDER_ID"}'
Response (200 OK):
{
"success": true,
"moved": ["A.png"],
"errors": [
{ "filename": "B.jpg", "error": "File not found or not owned by user" }
],
"message": "Successfully moved 1 file(s)"
}
GET /api/user/folders — List folders
Returns the folder list for the authenticated account.
Auth: x-api-key header required
The folder array is returned as data, not as a folders property.
Parameters:
x-api-key(header, required) — The authenticated account key.
curl "https://up.x02.me/api/user/folders" -H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": [
{
"id": "f1a2b3c4",
"name": "Screenshots",
"userId": "550e8400-e29b-41d4-a716-446655440000"
}
]
}
POST /api/user/folders — Create a folder
Creates a new folder for the authenticated account.
Auth: x-api-key header required
Names are capped at 40 characters and must be unique for the account. Free accounts are limited to 10 folders; Pro is unlimited.
Parameters:
name(json body, required) — Folder name to create. Max 40 characters.x-api-key(header, required) — The authenticated account key.
curl -X POST "https://up.x02.me/api/user/folders" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Screenshots"}'
Response (200 OK):
{
"success": true,
"data": {
"id": "f1a2b3c4",
"name": "Screenshots",
"userId": "550e8400-e29b-41d4-a716-446655440000"
}
}
DELETE /api/user/folders/:id — Delete a folder
Deletes a folder by its stored ID.
Auth: x-api-key header required
Parameters:
id(path, required) — Folder ID to remove.x-api-key(header, required) — The authenticated account key.
curl -X DELETE "https://up.x02.me/api/user/folders/YOUR_FOLDER_ID" \
-H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"message": "Folder deleted successfully"
}
Folder keys
Per-folder upload keys that drop files straight into one folder. Pro plan.
GET /api/user/folder-keys — List folder keys
Returns every folder key on the account, newest first, with the folder each one targets.
Auth: x-api-key header required (Pro plan)
A folder key is a dedicated key bound to one folder. Send it as x-api-key on an upload and the file lands in that folder automatically — no folderId needed. Keys look like fk_<6 hex>_<32 hex>. Returns 403 on non-Pro accounts.
Parameters:
x-api-key(header, required) — Your primary account key.
curl "https://up.x02.me/api/user/folder-keys" -H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": [
{
"id": "3f9c1a02-55d1-4a1e-9b7c-2c1d0e8a44b1",
"folderId": "f1a2b3c4",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"apiKey": "fk_a1b2c3_4d5e6f708192a3b4c5d6e7f809a1b2c3",
"createdAt": "2026-08-02T11:04:00.000Z",
"lastUsedAt": "2026-09-07T18:22:10.000Z",
"folderName": "Screenshots"
}
]
}
POST /api/user/folders/:id/key — Create or rotate a folder key
Issues a folder key for one folder. If that folder already has a key, the old one is replaced.
Auth: x-api-key header required (Pro plan)
This is create-or-rotate, not create-only: calling it again on the same folder invalidates the previous key immediately and resets lastUsedAt. The folder must belong to you.
Parameters:
id(path, required) — Folder ID to bind the key to.x-api-key(header, required) — Your primary account key.
curl -X POST "https://up.x02.me/api/user/folders/YOUR_FOLDER_ID/key" \
-H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": {
"id": "3f9c1a02-55d1-4a1e-9b7c-2c1d0e8a44b1",
"folderId": "f1a2b3c4",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"apiKey": "fk_a1b2c3_4d5e6f708192a3b4c5d6e7f809a1b2c3",
"createdAt": "2026-09-08T12:00:00.000Z",
"lastUsedAt": null,
"folderName": "Screenshots"
}
}
POST /api/upload — Upload with a folder key
Send a folder key as x-api-key and the upload is filed into that folder automatically.
Auth: Folder key in the x-api-key header (Pro plan)
Same endpoint as a normal upload — only the key changes. There is no folderId parameter; the destination comes from the key itself, which makes these keys convenient for ShareX profiles, cron jobs, and CI. If the account drops off Pro the key stops working with 403. Uploading with your primary key instead files into the account's default upload folder (see Set default upload folder).
Parameters:
x-api-key(header, required) — A folder key (fk_...). The upload destination is derived from it.file(form-data, required) — The file payload. Field name file or image.expiry(form-data) — Optional expiry such as 1h, 6h, 1d, 7d, or 30d.format(query) — Optional. Set to json for a JSON response instead of plain text.
curl -X POST "https://up.x02.me/api/upload?format=json" \
-H "x-api-key: fk_a1b2c3_4d5e6f708192a3b4c5d6e7f809a1b2c3" \
-F "[email protected]"
Response (200 OK):
{
"success": true,
"data": {
"url": "https://developer1.x02.me/i/ABCD.png",
"filename": "ABCD.png",
"originalFilename": "screenshot.png",
"mimeType": "image/png",
"sizeBytes": 245678,
"expiry": null
}
}
DELETE /api/user/folders/:id/key — Revoke a folder key
Deletes the folder's key. Any client still using it starts failing immediately.
Auth: x-api-key header required
Parameters:
id(path, required) — Folder ID whose key should be revoked.x-api-key(header, required) — Your primary account key.
curl -X DELETE "https://up.x02.me/api/user/folders/YOUR_FOLDER_ID/key" \
-H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"message": "Folder API key revoked successfully"
}
POST /api/user/default-folder — Set default upload folder
Chooses where uploads made with your primary API key are filed when no folder key is used.
Auth: x-api-key header required
Send folderId: null to go back to the root library. Folder keys ignore this setting — they always use their own folder.
Parameters:
x-api-key(header, required) — The authenticated account key.folderId(json body) — Folder ID to upload into, or null for the root library.
curl -X POST "https://up.x02.me/api/user/default-folder" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"folderId":"YOUR_FOLDER_ID"}'
Response (200 OK):
{
"success": true,
"defaultUploadFolderId": "YOUR_FOLDER_ID"
}
Authentication & accounts
Manage developer identity, registration, and API keys.
POST /api/auth/login — Account login
Authenticates with password credentials and returns the account profile plus a session token.
Auth: No authentication required (requires Cloudflare Turnstile token)
Parameters:
username(json body, required) — Registered username.password(json body, required) — Account password.turnstileToken(json body, required) — Active Cloudflare Turnstile captcha token.
curl -X POST "https://up.x02.me/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"developer1","password":"supersecretpassword","turnstileToken":"0.XTX..."}'
Response (200 OK):
{
"success": true,
"data": {
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"username": "developer1",
"apiKey": "b3f6a2e8c9d04f12ab345cd678ef9012",
"createdAt": "2026-05-18T10:00:00.000Z",
"uploadsFolder": "uploads/users/developer1",
"email": "[email protected]",
"plan": "basic",
"limits": {
"dailyLimit": 250,
"monthlyStorageLimit": 5368709120
}
},
"sessionToken": "d3b07384-d113-4c07-a3d5-0f62d1c68e1a"
}
}
GET /api/auth/me — Get profile info
Returns the full account profile for the authenticated key owner.
Auth: x-api-key header required
Use this to validate a key before making other authenticated calls. Returns 401 when the key is not recognized.
Parameters:
x-api-key(header, required) — The authenticated account key.
curl "https://up.x02.me/api/auth/me" -H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": {
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"username": "developer1",
"apiKey": "YOUR_API_KEY",
"createdAt": "2026-05-18T10:00:00.000Z",
"uploadsFolder": "uploads/users/developer1",
"email": "[email protected]",
"plan": "basic",
"status": "active",
"limits": {
"dailyLimit": 250,
"monthlyStorageLimit": 5368709120
},
"usage": {
"count": 128,
"lastUsed": "2026-09-07T18:22:10.000Z",
"monthlyStorageUsed": 734003200,
"currentMonth": "2026-09"
},
"features": {
"subdomain": true,
"shortUrl": false,
"folders": true
},
"customDomain": null,
"customDomainStatus": "pending",
"defaultUploadFolderId": null
}
}
}
POST /api/user/regenerate-api-key — Regenerate API key
Rotates your primary API key. The previous key is immediately revoked, so every third-party integration using it (ShareX and similar) stops working until updated.
Auth: x-api-key header required
Parameters:
x-api-key(header, required) — The authenticated account key to rotate.
curl -X POST "https://up.x02.me/api/user/regenerate-api-key" -H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": {
"apiKey": "f7c9e0a2d3b45c6789e0123f456789ab",
"limits": {
"dailyLimit": 250,
"monthlyStorageLimit": 5368709120
},
"regenerationInfo": {
"remainingThisMonth": "Unlimited",
"lastRegeneration": "2026-05-18T10:59:45.000Z"
}
}
}
System & settings
Platform configuration, URL format preferences, and custom domains.
GET /api/upload/config — Discover upload config
Returns the supported expiry options and the file size ceiling for each account tier.
Auth: No authentication required
Send x-api-key to have registered reflect your own tier. Without a key, registered falls back to the 200MB free-plan value.
Parameters:
x-api-key(header) — Optional. Resolves registered against your plan.
curl "https://up.x02.me/api/upload/config"
Response (200 OK):
{
"expiryOptions": [
{ "value": "", "label": "Never expires" },
{ "value": "1h", "label": "1 Hour" },
{ "value": "6h", "label": "6 Hours" },
{ "value": "1d", "label": "1 Day" },
{ "value": "7d", "label": "1 Week" },
{ "value": "30d", "label": "1 Month" }
],
"maxFileSize": {
"anonymous": 83886080,
"free": 209715200,
"pro": 536870912,
"registered": 209715200
}
}
GET /api/user/subdomain — Read URL format settings
Fetches the account's current URL format preference (subdomain, short, or custom domain) plus the resolved URL for each style.
Auth: x-api-key header required
Parameters:
x-api-key(header, required) — The authenticated account key.
curl "https://up.x02.me/api/user/subdomain" -H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": {
"format": "subdomain",
"subdomainUrl": "https://developer1.x02.me/i/filename",
"standardUrl": "https://x02.me/u/filename",
"customDomainActive": false,
"customDomainUrl": null
}
}
POST /api/user/subdomain — Update URL format preference
Sets which link style new uploads use going forward: personal subdomain, short URL, or a verified custom domain (Pro plan required for custom). Existing uploads keep whichever format they were created with — this never rewrites past links.
Auth: x-api-key header required
Parameters:
x-api-key(header, required) — The authenticated account key.format(json body, required) — One of "subdomain", "short", or "custom". "custom" requires an already-verified, active custom domain — see the custom domain endpoints below.
curl -X POST "https://up.x02.me/api/user/subdomain" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"format":"subdomain"}'
Response (200 OK):
{
"success": true,
"data": {
"format": "subdomain",
"message": "URL format updated to subdomain"
}
}
GET /api/user/custom-domain — Read custom domain configuration
Fetches the currently configured custom domain (if any), its verification status, the DNS records to add, and a live per-record verification check. Pro plan required.
Auth: x-api-key header required (Pro plan)
Parameters:
x-api-key(header, required) — The authenticated account key. Returns 403 on non-Pro accounts.
curl "https://up.x02.me/api/user/custom-domain" -H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": {
"customDomain": "img.yourbrand.com",
"customDomainStatus": "pending",
"txtRecordName": "_cf-custom-hostname.img.yourbrand.com",
"txtRecordValue": "b0970e66-8cc4-47bd-8189-f4827767037a",
"sslValidationRecords": [
{ "txt_name": "_acme-challenge.img.yourbrand.com", "txt_value": "..." }
],
"cnameTarget": "x02.me",
"dnsChecks": { "cname": false, "txt_val": false, "ssl_val_0": false }
}
}
POST /api/user/custom-domain — Add or replace custom domain
Registers a new custom hostname with Cloudflare and returns the DNS records (1 CNAME + 2 TXT) that must be added before it can verify. Pro plan required.
Auth: x-api-key header required (Pro plan)
Parameters:
x-api-key(header, required) — The authenticated account key.domain(json body, required) — Fully-qualified domain you own, e.g. "img.yourbrand.com". Cannot be x02.me or a subdomain of it.
curl -X POST "https://up.x02.me/api/user/custom-domain" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domain":"img.yourbrand.com"}'
Response (200 OK):
{
"success": true,
"data": {
"customDomain": "img.yourbrand.com",
"customDomainStatus": "pending",
"txtRecordName": "_cf-custom-hostname.img.yourbrand.com",
"txtRecordValue": "b0970e66-8cc4-47bd-8189-f4827767037a",
"sslValidationRecords": [],
"cnameTarget": "x02.me"
}
}
POST /api/user/custom-domain/verify — Verify custom domain
Re-checks the domain's DNS/SSL status with Cloudflare. Once both ownership and SSL are active, X02 activates routing and this domain becomes selectable as your "custom" URL format. Pro plan required.
Auth: x-api-key header required (Pro plan)
Parameters:
x-api-key(header, required) — The authenticated account key.
curl -X POST "https://up.x02.me/api/user/custom-domain/verify" -H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": {
"customDomain": "img.yourbrand.com",
"customDomainStatus": "active",
"cloudflareStatus": "active",
"sslStatus": "active",
"sslValidationRecords": [],
"dnsChecks": {}
}
}
DELETE /api/user/custom-domain — Remove custom domain
Deletes the custom hostname from Cloudflare and clears the configuration. Any uploads already using "custom" format automatically fall back to subdomain URLs. Pro plan required.
Auth: x-api-key header required (Pro plan)
Parameters:
x-api-key(header, required) — The authenticated account key.
curl -X DELETE "https://up.x02.me/api/user/custom-domain" -H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"message": "Custom domain configuration removed successfully."
}
Notifications & feedback
Read platform notifications and participate in active feedback polls.
GET /api/user/notifications — Fetch notifications
Returns the 50 most recent notifications for your account, plus the unread count.
Auth: x-api-key header required
Timestamps are returned as timestamp, and per-notification payloads live in data — not createdAt/metadata. A server-sent events stream is also available at /api/user/notifications/stream.
Parameters:
x-api-key(header, required) — The authenticated account key.
curl "https://up.x02.me/api/user/notifications" -H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": {
"notifications": [
{
"id": "notif-1234",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"type": "welcome",
"title": "Welcome to X02",
"message": "Hi developer1! We're thrilled to have you here.",
"read": false,
"data": {
"link": "/",
"linkText": "Start Uploading"
},
"broadcastId": null,
"timestamp": "2026-05-18T10:00:00.000Z"
}
],
"unreadCount": 1
}
}
POST /api/user/notifications/read — Mark as read
Marks a single notification as read using its unique ID.
Auth: x-api-key header required
Parameters:
x-api-key(header, required) — The authenticated account key.notificationId(json body, required) — The ID of the notification to mark read.
curl -X POST "https://up.x02.me/api/user/notifications/read" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"notificationId":"notif-1234"}'
Response (200 OK):
{
"success": true,
"message": "Notification marked as read"
}
GET /api/user/polls — Fetch feedback polls
Returns feedback polls visible to your account, active ones first.
Auth: x-api-key header required
Poll state is reported as status ('active' or 'closed'), your own choice as userVote, and per-option tallies as voteCount.
Parameters:
x-api-key(header, required) — The authenticated account key.
curl "https://up.x02.me/api/user/polls" -H "x-api-key: YOUR_API_KEY"
Response (200 OK):
{
"success": true,
"data": {
"polls": [
{
"id": "poll-5678",
"question": "Which programming language SDK would you like to see next?",
"description": "",
"status": "active",
"targetType": "all",
"targetUserId": null,
"targetUsername": null,
"createdAt": "2026-08-01T09:00:00.000Z",
"closedAt": null,
"totalVotes": 222,
"userVote": null,
"options": [
{ "id": "opt-rust", "text": "Rust SDK", "voteCount": 124 },
{ "id": "opt-go", "text": "Go SDK", "voteCount": 98 }
]
}
]
}
}
POST /api/user/polls/vote — Cast a poll vote
Casts a single vote on a poll option and returns the updated poll.
Auth: x-api-key header required
One vote per account per poll. Voting twice returns 400, and voting on a closed poll returns 400.
Parameters:
x-api-key(header, required) — The authenticated account key.pollId(json body, required) — The ID of the target poll.optionId(json body, required) — The ID of the chosen option.
curl -X POST "https://up.x02.me/api/user/polls/vote" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"pollId":"poll-5678","optionId":"opt-rust"}'
Response (200 OK):
{
"success": true,
"poll": {
"id": "poll-5678",
"question": "Which programming language SDK would you like to see next?",
"status": "active",
"totalVotes": 223,
"userVote": "opt-rust",
"options": [
{ "id": "opt-rust", "text": "Rust SDK", "voteCount": 125 },
{ "id": "opt-go", "text": "Go SDK", "voteCount": 98 }
]
}
}
Vision NSFW
Status and capability endpoints for the media moderation service, hosted separately at vision.x02.me.
GET /health — Health check
Verifies service status, model information, and queue configuration.
Auth: No authentication required
curl "https://vision.x02.me/health"
Response (200 OK):
{
"status": "healthy",
"service": "x02 vision v2",
"model": "EfficientNet-B4",
"device": "cuda",
"timestamp": "2026-04-06T12:00:00.000000+00:00",
"max_concurrent_jobs": 4,
"queue_worker_count": 2,
"queued_jobs": 0
}
GET /info — Service information
Returns service capabilities, supported formats, score thresholds, and the category map.
Auth: No authentication required
thresholds is the key to reading any moderation result: below 40 allows, 40-80 soft flags, above 80 hard blocks.
curl "https://vision.x02.me/info"
Response (200 OK):
{
"service": "x02 Unified Media Moderation v2",
"model": "EfficientNet-B4",
"supported_formats": {
"images": ["jpg", "jpeg", "png", "webp", "bmp"],
"videos": ["mp4", "avi", "mov", "mkv", "webm", "flv", "wmv", "m4v"]
},
"max_file_size_mb": 500,
"endpoints": {
"health": "GET /health",
"image": "POST /moderate-image",
"gif": "POST /moderate-gif",
"video": "POST /moderate-video",
"auto": "POST /moderate-media",
"queue_gif": "POST /jobs/moderate-gif",
"queue_video": "POST /jobs/moderate-video",
"job_status": "GET /jobs/{job_id}",
"job_result": "GET /jobs/{job_id}/result"
},
"categories": {
"0": "hentai",
"1": "nsfw_real",
"2": "sfw_anime",
"3": "sfw_real"
},
"thresholds": {
"allow": "< 40",
"soft_flag": "40-80",
"hard_block": "> 80"
},
"max_concurrent_jobs": 4,
"queue_worker_count": 2
}
FAQ
Do I give an AI client my API key?
No. The MCP server uses OAuth: the client sends you to x02.me to sign in, you approve the scopes it asked for, and it receives its own token. Your API key never leaves X02, and you can revoke a client's access without rotating the key.
What does the upload API return by default?
POST /api/upload returns plain text by default, containing the hosted file URL. This keeps existing upload clients working without any response-shape change.
How do I get JSON from the upload API?
Use either ?format=json on the upload URL or send the x-response-format: json header. That returns a JSON payload with the hosted URL, stored filename, original filename, mime type, size in bytes, and expiry.
Can I upload anonymously from my own app or script?
No. Anonymous uploads are only accepted for same-origin requests coming from the X02 website. Any request from outside x02.me must send an x-api-key header, otherwise it is rejected with 401.
Why does my dashboard integration see an empty file list?
Uploads are returned as data.uploads, not as a top-level images array. Folders, folder keys, pagination and the account profile are all nested under data as well.
Should I store the plain-text upload response or the JSON response fields?
If you are building an integration, prefer the JSON mode and store both the final hosted URL and the stored filename. Those values are safer for later rename, move, info, and delete operations.
Why can delete or info fail when I use the original local filename?
The stored filename can differ from the original local filename after upload. Use the filename or URL returned by the upload response, or inspect the account with list/info before follow-up operations.
Does the upload API accept multiple files in one request?
No — one file per request. Send the batch as repeated uploads, one call per file.
Can I upload an image from a remote URL?
Yes. Use POST /api/upload/url with a JSON body containing the imageUrl parameter. The URL must resolve to an image content type, and internal or private hostnames are rejected.
Can I send expiry with the upload API?
Yes. Send expiry as form-data using supported values such as 1h, 6h, 1d, 7d, or 30d. If expiry is omitted, the upload keeps the default non-expiring behavior.
Which routes require the x-api-key header?
Dashboard, file management, folder management, account, notification, and poll routes require x-api-key. Public connectivity checks and the upload config route do not.
How do I tell which files a batch move actually moved?
PATCH /api/user/images/move is partially successful by design. Read the moved array for filenames that succeeded and the errors array for the ones that were skipped — the top-level success flag is true even when some files fail.
How do I upload straight into a specific folder?
Generate a folder key for that folder (POST /api/user/folders/:id/key) and send it as x-api-key on the upload. The destination comes from the key, so there is no folderId parameter to pass — which makes folder keys a good fit for ShareX profiles, cron jobs, and CI. Folder keys are Pro-only and start with fk_.
What happens if I generate a folder key twice for the same folder?
The second call rotates the key: the previous one is replaced and stops working immediately. There is only ever one active key per folder, so re-issuing is how you rotate a leaked key.
Where do uploads go when I use my primary API key instead of a folder key?
Into the account's default upload folder, or the root library if none is set. Change it with POST /api/user/default-folder.
Which identifier should I store in my own system after upload?
Store the hosted URL and the final stored filename returned by X02. Those values are safer for later info, rename, move, and delete operations than the original local filename alone.
When should I use the MCP server instead of the REST API?
Use MCP when an AI assistant is doing the work for you and you want it scoped and revocable. Use the REST API when X02 is part of your own backend, automation stack, or third-party integration — anywhere you control the code and an API key is the simpler credential.
How do I revoke an AI client's access?
Revoking is per client, not per account: remove the connection and that client's tokens stop working immediately. Other clients, your API key, and your folder keys are unaffected.