Skip to main content

X02 API documentation

X02 has two programmatic interfaces:

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:

Scopes:

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:

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:

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": "..." }):

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:

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": "..." }):

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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.