# X02 — full text for language models > Everything on https://x02.me worth reading, in one file. The short index is https://x02.me/llms.txt. --- # X02 — free file hosting with an API and an MCP server X02 ([x02.me](https://x02.me)) hosts images, video, audio, PDFs, documents, archives, fonts and 3D models and gives each one a direct link. Upload in the browser without an account, or sign up for an API key, folders, shared albums and your own subdomain (`username.x02.me`). Uploaded images are screened by X02 Vision, an in-house NSFW classifier. AI assistants can use X02 directly. X02 runs a remote [Model Context Protocol](https://modelcontextprotocol.io) 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. Setup for each app: [Use X02 with AI assistants](https://x02.me/ai). ## Ways to upload - **Browser:** drag, paste or pick a file at https://x02.me. No account needed; anonymous uploads can be set to expire after an hour, a day, a week or a month. - **AI assistant:** connect `https://up.x02.me/mcp` and ask. See [x02.me/ai](https://x02.me/ai). - **REST API:** `POST https://up.x02.me/api/upload` with an `x-api-key` header. Reference: [API documentation](https://x02.me/api-docs). - **ShareX:** [ShareX integration guide](https://x02.me/blog/how-to-integrate-sharex-with-x02). ```bash curl -X POST "https://up.x02.me/api/upload?format=json" -H "x-api-key: YOUR_API_KEY" -F "file=@image.png" ``` ## Plans | | Free | Pro | |---|---|---| | Price | $0, forever | $3.69 per month | | Storage | 5 GB | 50 GB | | Largest single file | 200 MB (80 MB without an account) | 512 MB | | Uploads per day | 250 | 1,500+ | | API access and subdomain URLs | Yes | Yes | | Custom folders | Up to 10 | Unlimited | | Private files and folders, password-protected albums | No | Yes | | Folder API keys (upload straight into a folder, e.g. from ShareX) | No | Yes | | Folder collaboration | No | Yes | | Custom domain for file links | No | Yes | ## Supported file types - **Images:** PNG, JPG, JPEG, GIF, WebP, AVIF, SVG - **Videos:** MP4, WebM, AVI, MKV, MOV - **Audio:** MP3, WAV, OGG, M4A, FLAC - **Documents:** PDF, DOCX, XLSX, PPTX, TXT, EPUB - **Archives:** ZIP, RAR, 7Z, TAR, GZ - **3D Models:** STL, OBJ, FBX, GCODE - **Fonts:** TTF, OTF, WOFF, WOFF2 ## Frequently asked questions ### Is X02 free to use? Yes. You can upload files for free, and anonymous uploads are available without creating an account. ### Do I need an account to upload files? No. Anonymous uploads are supported, but creating an account unlocks API access, custom subdomains, and extra account features. ### What file types can I upload? X02 supports a wide range of formats including images (PNG, JPG, JPEG, GIF, WebP, SVG), videos (MP4, WebM, AVI, MKV, MOV), audio (MP3, WAV, OGG, M4A, FLAC), documents (PDF, DOC/DOCX, XLS/XLSX, PPT/PPTX, TXT), and compression archives (ZIP, RAR, 7Z, TAR, GZ). ### Does X02 provide an API? Yes. Developers can use the X02 API for uploads and file management, with documentation available on the API docs page. ### Can I use X02 from ChatGPT, Claude or another AI assistant? Yes. Add https://up.x02.me/mcp as a connector in Claude, ChatGPT, Gemini, Grok or Le Chat, sign in to X02 once, and the assistant can upload, find, organize and share your files for you. Setup steps for each app are at x02.me/ai. ## More - [Use X02 with AI assistants (MCP)](https://x02.me/ai) - [How X02 works](https://x02.me/how-it-works) - [API documentation](https://x02.me/api-docs) - [Blog and guides](https://x02.me/blog) - [Changelog](https://x02.me/changelog) - [llms.txt](https://x02.me/llms.txt) and [llms-full.txt](https://x02.me/llms-full.txt) — this site, for language models --- # Use X02 in Claude, ChatGPT, Gemini and other AI chat apps X02 runs a remote [Model Context Protocol](https://modelcontextprotocol.io) 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. **Server address:** `https://up.x02.me/mcp` It is not a secret — it is the address the AI app connects to. The app discovers the sign-in endpoints itself, opens x02.me so the user can sign in, and stores its own token. The connector is free on every X02 account; some AI apps put custom connectors behind their own paid plans. ## What you can ask - "Find the screenshot I uploaded last Tuesday and tell me what's in it." - "Upload https://example.com/logo.png to my X02 and give me the link." - "Put this week's uploads in a folder called Client Work and share it." - "How much of today's upload quota do I have left?" - "Rename my last five uploads to something readable." - "I've got a photo on my laptop I want up there." ## Connect it You need an X02 account first. ### Claude 1. In a chat, open the + menu and choose Browse connectors. 2. Search for X02 and click Add. 3. Sign in to X02 in the window that opens, review the permissions (untick any you would rather not give), and click Allow Access. Open: https://claude.ai/customize/connectors X02 is listed in Anthropic's connector directory. To add it by hand instead, open Customize → Connectors → + → Add custom connector and paste the server address. It then follows you across claude.ai, the desktop app and mobile. ### ChatGPT 1. Open Settings → Apps & Connectors → Advanced settings and turn on Developer mode. 2. Add a connector and paste the server address. 3. Sign in to X02 when the browser opens. Open: https://chatgpt.com/ Needs a paid plan (Plus, Pro, Business, Enterprise or Edu) and works on the web only. Without developer mode ChatGPT exposes only search and fetch tools, and X02 has neither. ### Gemini 1. On gemini.google.com, open Settings → Connected Apps. 2. Under Custom apps, click Add a custom app and paste the server address. 3. Click Next and sign in to X02 when asked. Open: https://gemini.google.com/ Google currently limits custom apps to personal Google accounts, in the US, aged 18 or over, with Keep Activity turned on. Add it on the web; it then works in the Gemini mobile app too. ### Grok 1. Go to grok.com/connectors and click New Connector → Custom. 2. Paste the server address. 3. Sign in to X02 when asked. Open: https://grok.com/connectors xAI documents custom connectors for Business and Enterprise plans. ### Le Chat 1. Open Connectors from the sidebar and click + Add Connector. 2. Switch to the Custom MCP Connector tab, name it x02 and paste the server address. 3. Click Connect and sign in to X02 when asked. Open: https://chat.mistral.ai/ Available on every Mistral plan, including Free. On a team plan, only an admin can add connectors. ### Another app 1. Look in the app's settings for Connectors, Integrations, Apps or MCP. 2. Choose the option to add your own (Custom, Add custom connector, Add server) and paste the server address. 3. Sign in to X02 when your browser opens, check the permissions, and approve. The app has to support remote connectors with sign-in. If it only asks for a command to run, or only takes an API key, X02 cannot be added there. To check it worked, ask "what x02 tools do you have?" — there should be fifteen. Full walkthrough: [Set up X02 in Claude, ChatGPT, Grok and more](https://x02.me/blog/set-up-x02-mcp-in-your-ai-client). ### Developer tools Claude Code, Cursor, VS Code and Windsurf connect with the same address; choose HTTP or Streamable HTTP as the transport, not stdio. In Claude Code: ```bash claude mcp add --transport http x02 https://up.x02.me/mcp ``` Details: [developer setup](https://x02.me/blog/set-up-x02-mcp-in-your-ai-client#claude-code). ## 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. ## Permissions Requested when you connect, shown on the consent screen (each with a checkbox, so any can be withheld), and checked on every call: - `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. The account owner can trim an app's permissions or disconnect it under Account → Connected apps in the X02 dashboard, which takes effect immediately; removing the connector in the app's own settings also works. Either way only that app is affected: the account's API key, folder keys and other connected apps are unaffected. ## Questions ### What is the X02 MCP server? A connector that lets an AI chat app work on your X02 files. Add https://up.x02.me/mcp to Claude, ChatGPT, Gemini, Grok, Le Chat or another app that supports connectors, and the assistant can upload, search, view, rename, move, delete, organize into folders and share albums for you. MCP (Model Context Protocol) is the open standard these apps use to connect to outside services. ### Do I have to paste my API key into the AI app? No. Connecting opens x02.me in your browser, you sign in and approve the permissions the app asked for, and the app receives its own token. Your API key never leaves X02. ### Is it free? Yes. The connector works on free X02 accounts, with your plan's normal upload limits. Some AI apps put custom connectors behind their own paid plans — ChatGPT, for example. ### Can the assistant upload a file I attached to the chat? Not directly — AI apps have no way to pass an attachment on to a connector. The assistant hands you a drop link instead, and you paste or drag the file into it. Images already online can be rehosted by URL with no extra step. ### How do I disconnect an app? Open Account → Connected apps in your X02 dashboard and click Disconnect; that ends the app's access immediately. Removing the connector in the app's own settings also works, though how quickly depends on the app. Either way your API key, folder keys and any other connected apps are unaffected. ### Which permissions does it ask for? Up to six, shown on the consent screen before you approve: read your account, read files, upload and edit files, delete files, read folders, and manage folders. Each has a checkbox, so you can untick any you would rather not give, and trim them later under Account → Connected apps. Anything outside what you approved is refused. ### Does it work in code editors too? Yes — Claude Code, Cursor, VS Code and Windsurf all connect with the same address. Part 2 of the setup guide has the details. ## Guides - [Set up X02 in Claude, ChatGPT, Grok and more](https://x02.me/blog/set-up-x02-mcp-in-your-ai-client): Step-by-step for every app, with the plan requirements that trip people up. - [Ten things to ask an assistant connected to X02](https://x02.me/blog/x02-mcp-workflows-and-recipes): Worked examples, from finding a file by its old name to publishing an album. - [How to send files to X02 from an AI chat](https://x02.me/blog/how-to-send-files-to-x02-from-an-ai-chat): Drop links, the standing tray, and rehosting from a URL. - [What you are actually approving](https://x02.me/blog/x02-mcp-permissions-and-scopes-explained): Which permission governs what, and what disconnecting really does. - [Troubleshooting X02 connections](https://x02.me/blog/troubleshooting-x02-mcp-connections): Every way a connection fails, what the error means, and the fix. - [Developer reference](https://x02.me/api-docs#mcp): MCP and REST API docs. Machine-readable: [MCP server card](https://x02.me/.well-known/mcp.json), [OAuth protected-resource metadata](https://up.x02.me/.well-known/oauth-protected-resource/mcp). --- # 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](https://x02.me/ai). - **REST API** at `https://up.x02.me/api`, for your own code. Authenticate with an `x-api-key` header; get a key from the dashboard ([how](https://x02.me/blog/how-to-get-an-api-key)). Vision endpoints live at `https://vision.x02.me`. ## MCP server X02 runs a remote [Model Context Protocol](https://modelcontextprotocol.io) 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. ```bash 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 ```bash curl "https://up.x02.me/api/ping" ``` Response (200 OK): ```json { "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. ```bash curl "https://up.x02.me/api/delete/ABCD.png?apiKey=YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash curl -X POST "https://up.x02.me/api/upload?format=json" \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@image.png" \ -F "expiry=1d" ``` Response (200 OK): ```json { "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. ```bash 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): ```json { "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. ```bash curl "https://up.x02.me/api/user/dashboard?page=1&limit=20" \ -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "success": true, "data": { "user": { "id": "550e8400-e29b-41d4-a716-446655440000", "username": "developer1", "email": "user@example.com", "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. ```bash curl -X DELETE "https://up.x02.me/api/user/images/ABCD.png" \ -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash 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): ```json { "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. ```bash curl -X PATCH "https://up.x02.me/api/user/images/ABCD.png/replace" \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@updated.png" ``` Response (200 OK): ```json { "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. ```bash 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): ```json { "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. ```bash 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): ```json { "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. ```bash curl "https://up.x02.me/api/user/folders" -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash 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): ```json { "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. ```bash curl -X DELETE "https://up.x02.me/api/user/folders/YOUR_FOLDER_ID" \ -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash curl "https://up.x02.me/api/user/folder-keys" -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash curl -X POST "https://up.x02.me/api/user/folders/YOUR_FOLDER_ID/key" \ -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash curl -X POST "https://up.x02.me/api/upload?format=json" \ -H "x-api-key: fk_a1b2c3_4d5e6f708192a3b4c5d6e7f809a1b2c3" \ -F "file=@screenshot.png" ``` Response (200 OK): ```json { "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. ```bash curl -X DELETE "https://up.x02.me/api/user/folders/YOUR_FOLDER_ID/key" \ -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash 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): ```json { "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. ```bash 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): ```json { "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": "user@example.com", "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. ```bash curl "https://up.x02.me/api/auth/me" -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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": "user@example.com", "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. ```bash curl -X POST "https://up.x02.me/api/user/regenerate-api-key" -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash curl "https://up.x02.me/api/upload/config" ``` Response (200 OK): ```json { "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. ```bash curl "https://up.x02.me/api/user/subdomain" -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash 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): ```json { "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. ```bash curl "https://up.x02.me/api/user/custom-domain" -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash 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): ```json { "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. ```bash curl -X POST "https://up.x02.me/api/user/custom-domain/verify" -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash curl -X DELETE "https://up.x02.me/api/user/custom-domain" -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash curl "https://up.x02.me/api/user/notifications" -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash 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): ```json { "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. ```bash curl "https://up.x02.me/api/user/polls" -H "x-api-key: YOUR_API_KEY" ``` Response (200 OK): ```json { "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. ```bash 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): ```json { "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 ```bash curl "https://vision.x02.me/health" ``` Response (200 OK): ```json { "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. ```bash curl "https://vision.x02.me/info" ``` Response (200 OK): ```json { "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. --- # How X02 works 1. **Upload your files.** Drag and drop, paste, or browse at https://x02.me — images, video, audio, PDFs, office documents, archives, fonts and 3D models. 2. **Get a shareable link.** Every upload gets a short direct URL, on `x02.me/u/...` or your own `username.x02.me` subdomain. 3. **Manage your files.** Search, rename and organise uploads into folders from the dashboard, which also shows your storage and daily limits. 4. **Automate it.** Use the [REST API](https://x02.me/api-docs), [ShareX](https://x02.me/blog/how-to-integrate-sharex-with-x02), or let an AI assistant do it through the [MCP server](https://x02.me/ai). ## Questions ### Is there a file size limit? Yes: 80 MB for anonymous uploads, 200 MB on a free account and 512 MB on Pro. ### How long are files stored? Indefinitely, unless you set an expiry when uploading or the file violates the terms of service. ### Can I delete my uploaded files? Registered users can delete any of their files from the dashboard or through the API. Deletions are permanent. ### How does X02 Vision work? Uploaded images are scanned by X02 Vision, an in-house classifier. Flagged images go to the moderation team for manual review before any action is taken. Technical write-up: [Real-time NSFW detection at scale](https://x02.me/blog/realtime-nsfw-detection-at-scale). ## Plans | | Free | Pro | |---|---|---| | Price | $0, forever | $3.69 per month | | Storage | 5 GB | 50 GB | | Largest single file | 200 MB (80 MB without an account) | 512 MB | | Uploads per day | 250 | 1,500+ | | API access and subdomain URLs | Yes | Yes | | Custom folders | Up to 10 | Unlimited | | Private files and folders, password-protected albums | No | Yes | | Folder API keys (upload straight into a folder, e.g. from ShareX) | No | Yes | | Folder collaboration | No | Yes | | Custom domain for file links | No | Yes | --- # How to Send Files to X02 From an AI Chat Source: https://x02.me/blog/how-to-send-files-to-x02-from-an-ai-chat 2026-09-22 · X02 Team "Upload this screenshot" is the single most common thing anyone asks an assistant to do with X02, and it is the one place where the protocol gets in the way. An AI client can *show* the model your attachment, but it has no mechanism for handing the bytes to a remote server — and no client exposes your attachments as public URLs, which is just as well. So X02 has four routes instead. This page is which one applies when, and what each costs you. > [!TIP] > **Quick Navigation:** > * [The four routes at a glance](#the-four-routes-at-a-glance) > * [The tray: drop first, ask later](#the-tray-drop-first-ask-later) > * [Drop links: ask first, drop after](#drop-links-ask-first-drop-after) > * [Rehosting from a URL](#rehosting-from-a-url) > * [Inline upload, and why it is so small](#inline-upload-and-why-it-is-so-small) > * [What happens after the upload](#what-happens-after-the-upload) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## The four routes at a glance | Route | Use it for | Ceiling | Expires | |---|---|---|---| | **The tray** (`x02.me/drop`) | Anything, when you know in advance you will want it | Your plan's file size limit | Drop waits 24 hours | | **Drop link** (`request_file_from_user`) | Anything, when the assistant asks mid-conversation | Your plan's file size limit | Link open 15 minutes | | **From a URL** (`upload_from_url`) | Images already online | Your plan's file size limit | — | | **Inline** (`upload_file`) | Icons, SVGs, short text files | 1 MB hard, ~128 KB comfortable | — | The pattern: **the two routes that go through the chat are the limited ones.** Anything that travels as text in a conversation is expensive and capped. The two that route around the conversation carry whatever your plan allows. ![Architecture Diagram](/blog-diagrams/how-to-send-files-to-x02-from-an-ai-chat-1.svg) --- ## The tray: drop first, ask later The best route, and the one nobody discovers on their own. Open **[x02.me/drop](https://x02.me/drop)** and pin it as a tab. Paste anything into it — Ctrl+V works anywhere on the page, which is the entire interaction for a screenshot already on your clipboard. Dragging files in works too. Whatever is sitting there gets picked up by the next assistant that asks. You never see a link, never wait, never go back and confirm. Ask "upload the screenshot I just dropped and give me the link" and it is already done by the time it answers. > [!NOTE] > A tray drop waits **24 hours** for someone to collect it. Unlike a drop link, it is not tied to one assistant — whichever one asks next gets it. That is the point: the tray is shared across your clients, a drop link is not. Each drop is handed over once. Collecting it marks it claimed, so asking a second assistant the same question does not re-deliver the same pile of files. --- ## Drop links: ask first, drop after When you ask for an upload and there is nothing in your tray, the assistant gives you a link: ``` https://x02.me/drop/K7M2QXR4 ``` Open it, paste or drag the file, and the assistant collects it without you saying anything. It polls in roughly ten-second stretches, so a "not yet" in the middle of the conversation is normal rather than a failure — it means the link is still open and it is still watching. Three things worth knowing: **The link lasts fifteen minutes.** Wander off and come back to an expired one and the assistant simply issues another. Nothing is lost. **The code is eight characters** from an alphabet with no O/0 or I/1, so reading one aloud or retyping it is survivable. **It is delivered to one assistant only.** A file handed to Claude through a drop link does not appear in Cursor. If you want something available to whichever assistant asks next, use the tray instead. --- ## Rehosting from a URL If the file is already online, this is the fastest route by a distance: give the assistant the URL and the bytes go straight from the source to X02 without passing through the conversation at all. > "Rehost https://example.com/diagram.png on my account and give me the link." Two constraints: - **Images only.** The URL has to answer with an `image/*` content type. A PDF, a video or an HTML page is refused with *"Only image URLs are supported"*. - **Private and internal hostnames are refused.** Localhost, the private IPv4 ranges, and `.local` and `.internal` hostnames are all blocked, so an assistant cannot be talked into fetching something off your network. Size follows your plan: 80 MB anonymous, 200 MB on a registered account, 512 MB on Pro. The remote file's `Content-Length` is checked before the download starts, and the real size again afterwards, so an oversized file fails fast rather than halfway. --- ## Inline upload, and why it is so small `upload_file` carries the file base64-encoded inside the tool call itself. That works, and for small things it is the most direct route there is — an SVG, a favicon, a config snippet the assistant just generated. For anything photographic it is hopeless, and the arithmetic is worth seeing. Base64 inflates bytes by about a third. A modest 150 KB screenshot becomes roughly 200,000 characters of text that has to pass through the model's context on the way. Most clients will not emit that much, and the ones that do make you pay for it twice. So: comfortable under **128 KB**, refused outright above **1 MB**. When it refuses, it says so in terms you can act on — upload it yourself on x02.me and tell the assistant what it was called, or hand over a URL instead. That first suggestion works better than it sounds, because of how search behaves. X02 renames files on upload, but it keeps your original name alongside the generated one, and `list_files` searches both. Upload `quarterly-report-final.png` through the website and then say "find the quarterly report I just uploaded" — the assistant finds it by the name it had on your machine, without you ever touching the generated one. --- ## What happens after the upload **The stored name is not your name.** X02 assigns a short generated filename, and that is what every other tool expects — `rename_files`, `move_files`, `delete_files`, `share_folder`. Assistants are told to use the name returned by the upload. If you are cross-referencing by hand, check `list_files` rather than assuming. **It lands in your default upload folder.** If you have set one, assistant uploads go there too, by every route. See [setting a default upload folder](/blog/how-to-set-a-default-upload-folder). **It counts against your normal quota.** MCP is not a separate allowance — same daily limit (250 uploads on a registered account, 1,500 on Pro), same per-plan file size ceiling. Before a big batch, ask: > "How many uploads do I have left today?" **You can set an expiry at upload time.** Both upload tools accept `1h`, `6h`, `1d`, `7d` or `30d`, after which the file deletes itself. Say "upload this but have it expire in a day" and the assistant passes it through. Omit it and the file is permanent. --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **What MCP is and what it can do** | [Connect AI Assistants to X02 with MCP](/blog/connect-ai-assistants-to-x02-with-mcp) | | **Step-by-step setup for your client** | [Set Up X02 in Claude, ChatGPT, Cursor and More](/blog/set-up-x02-mcp-in-your-ai-client) | | **What each permission grants** | [X02 MCP Permissions: What You Are Actually Approving](/blog/x02-mcp-permissions-and-scopes-explained) | | **Things to ask your assistant** | [Ten Things to Ask an Assistant Connected to X02](/blog/x02-mcp-workflows-and-recipes) | | **Upload files by every other method** | [How to Upload Files to X02](/blog/how-to-upload-files-to-x02) | | **Set a default upload folder** | [How to Set a Default Upload Folder](/blog/how-to-set-a-default-upload-folder) | --- # Set Up X02 in Claude, ChatGPT, Grok and More Source: https://x02.me/blog/set-up-x02-mcp-in-your-ai-client 2026-09-22 · X02 Team Your AI assistant can work with the files in your X02 account directly — find them, look at them, add new ones, sort them into folders and hand you share links. You ask in ordinary words and it does the clicking. Once it is connected, this works: > *"Find the screenshot I uploaded last Tuesday and tell me what's in it."* > > *"Put this week's uploads in a folder called Client Work and share it."* > > *"I've got a photo on my laptop I want up there."* Connecting takes about thirty seconds and one address that you copy and paste. There is nothing to install and nothing to configure. **Never done this before?** You do not need to understand anything technical to follow this page. You need two things: an X02 account, and an AI app that supports connections — Claude, ChatGPT, Grok, Cursor, Windsurf and VS Code all do, and so do a growing number of others. Find yours in the list below, or use [Any other app](#any-other-app) if it is not there. Every app hides the setting somewhere different, and a few have requirements nobody mentions until it quietly fails, which is why each one gets its own steps rather than a single instruction. Curious what is underneath it, or what an assistant is and is not allowed to do with your account? [Connect AI Assistants to X02 with MCP](/blog/connect-ai-assistants-to-x02-with-mcp) covers that. You do not need it to get set up. > [!TIP] > **Quick Navigation:** > > **Part 1 — for everyone** > * [The one thing you need](#the-one-thing-you-need) > * [Claude](#claude-web-desktop-and-mobile) > * [ChatGPT](#chatgpt) > * [Grok](#grok) > * [Gemini](#gemini) > * [Le Chat (Mistral)](#le-chat-mistral) > * [Any other app](#any-other-app) > * [Check it worked](#check-it-worked) > * [If something is not working](#if-something-is-not-working) > > **Part 2 — for developers** > * [Claude Code](#claude-code) > * [Cursor, Windsurf and VS Code](#cursor-windsurf-and-vs-code) > * [MCP Inspector](#mcp-inspector-for-when-something-breaks) > * [More troubleshooting](#more-troubleshooting) --- --- ## Part 1: The Easy Guide (For Everyone) Claude, ChatGPT, Grok, Gemini and Le Chat — and a general recipe for anything else. No typing beyond a copy and paste, and nothing to install. --- ### The one thing you need ``` https://up.x02.me/mcp ``` That address is the whole setup. Copy it now — every section below asks you to paste it somewhere. It is not a password and it is not secret, so it does not matter who sees it. It is closer to a shop address than a key: it tells your AI app where X02 lives. When you paste it in, the app works the rest out by itself, opens your browser so you can sign in to X02 the normal way, and remembers its own permission slip afterwards. You never copy a password or a key into the AI app. ![Architecture Diagram](/blog-diagrams/set-up-x02-mcp-in-your-ai-client-1.svg) > [!NOTE] > You need an X02 account before you start, because the assistant works on *your* files and you are the one who approves it. If you have only ever dropped files on the homepage without signing up, [make an account first](https://x02.me) — it takes a moment and raises your upload limits at the same time. --- ### Claude (web, desktop and mobile) #### The short way X02 is listed in Anthropic's own directory, so there is nothing to type: 1. Open the **+** menu and choose **Browse connectors** 2. Search for **X02** and click **Add** 3. A window opens on x02.me — sign in, read what it is asking permission to do, and click **Allow Access** That is it. Skip to [Check it worked](#check-it-worked). #### The manual way Use this if you cannot see the directory, or you would rather paste the address yourself. The result is identical — same connection, same sign-in, same everything. 1. Open **[Customize → Connectors](https://claude.ai/customize/connectors)** — the toolbox icon at the bottom of the sidebar, then **Customize**, then **Connectors** 2. Click **+** in the top right, then **Add custom connector** 3. Name it `x02`, paste `https://up.x02.me/mcp`, and save 4. A window opens on x02.me — sign in, review the permissions, and click **Allow Access** Either way, you add it once and it follows you across claude.ai, the desktop app and mobile, because the connection lives on your account rather than on one device. On **Team and Enterprise** plans, custom connectors are added by an organization owner under Organization settings → Connectors; members then connect their own X02 account to it. If you cannot see **Add custom connector** at all, that is why. > [!NOTE] > **Claude connects from Anthropic's servers, not your computer.** That is invisible for X02, which is on the public internet. It matters if you ever connect a *different* MCP server running on localhost or behind a company VPN — those cannot work this way, however well they run on your machine. --- ### ChatGPT ChatGPT calls these **apps**, and the setting is behind a flag: 1. Open **Settings → Apps & Connectors** 2. Scroll to the bottom, open **Advanced settings**, and turn on **Developer mode** — it is one level deeper than most guides suggest, which is why people report not finding it 3. Add a connector and paste `https://up.x02.me/mcp` 4. Sign in when the browser opens Three requirements that are easy to miss: - **A paid plan is required** — Plus, Pro, Business, Enterprise or Edu. Custom connectors are not available on the free tier. - **It is web only.** Custom MCP apps do not run in the ChatGPT mobile apps, so a connection you add on the web will not follow you to your phone. This is a ChatGPT limitation, not an X02 one — Claude does sync across devices. - **On Business and Enterprise, an admin may have to allow developer mode first.** If the toggle simply is not there, this is almost always why, and no amount of retrying will reveal it. > [!WARNING] > **Developer mode is not optional here.** Added as an ordinary connector, ChatGPT exposes only two tools from any MCP server — `search` and `fetch` — and X02 has neither. The connection looks fine, and then ChatGPT says the file operation "isn't exposed to me in the current toolset" and falls back to searching the web. Nothing is wrong with your account: turn developer mode on, re-add the server, and all fifteen tools appear. > > A quick check before anything else: ask **"what x02 tools do you have?"** If it cannot name `list_files`, developer mode is off. #### One quirk worth knowing Ask ChatGPT to show you an image and it will not appear in the conversation. ChatGPT does not render images returned by an MCP server — Cursor and several others behave the same way, and it is filed as a bug against them rather than something a server can fix. The picture does reach the model, so **"describe what's in it"** works and is accurate. Every `view_file` reply also carries a direct link to the full image, which opens normally in a browser. In Claude the image appears inline. --- ### Grok 1. Go to **[grok.com/connectors](https://grok.com/connectors)** 2. Click **New Connector**, then **Custom** 3. Paste `https://up.x02.me/mcp` and sign in when it asks > [!NOTE] > xAI documents custom connectors under **Business and Enterprise**, where an admin has to provision the connector in the cloud console before anyone on the team can use it. If you cannot see **New Connector** at all, that is the likely reason rather than anything you have done wrong. Grok will only talk to servers that are on the public internet. X02 is, so there is nothing extra to do — but it is why a server running on your own computer cannot be added this way. --- ### Gemini Google calls these **custom apps**: 1. On **[gemini.google.com](https://gemini.google.com)**, open **Settings → Connected Apps** 2. Under **Custom apps**, click **Add a custom app** and paste `https://up.x02.me/mcp` 3. Click **Next** and sign in to X02 when asked Add it on the web; once connected it works in the Gemini mobile app as well. > [!NOTE] > Google currently limits custom apps to **personal Google accounts** (not Workspace), **in the US**, **aged 18 or over**, with **Keep Activity** turned on. If you do not see **Custom apps** under Connected Apps, one of those is almost always why. --- ### Le Chat (Mistral) 1. Open **Connectors** from the sidebar and click **+ Add Connector** 2. Switch to the **Custom MCP Connector** tab, name it `x02`, and paste `https://up.x02.me/mcp` 3. Click **Connect** — Le Chat works out the sign-in method by itself — and sign in to X02 when asked Custom connectors are available on every Mistral plan, including Free. On a team plan, only an admin can add them. --- ### Any other app New apps add support for this every month, and the wording differs in each one. The shape is always the same: 1. Open the app's settings and look for **Connectors**, **Integrations**, **Tools** or **MCP** — one of those four covers almost everything 2. Choose whatever means *add your own*: **Custom**, **Add custom connector**, **Add server** 3. Paste `https://up.x02.me/mcp` 4. If it asks you to pick a type, choose **HTTP** or **Streamable HTTP**. Do not pick **stdio**, **local** or **command** — those are for programs running on your own machine, and X02 is not one 5. Sign in when your browser opens, check the permissions, approve Two things will stop it working, and neither is a setting you can find: - **The app has to support remote servers.** Some only run local ones. If the only option asks for a command to run rather than an address, X02 cannot be added there. - **The app has to support signing in.** A few older ones only take an API key in a header. X02 deliberately does not work that way, because the whole point is that you never paste a key into someone else's software. If you get stuck, the [MCP Inspector](#mcp-inspector-for-when-something-breaks) in Part 2 will tell you within a minute whether the problem is the app or X02. --- ### Check it worked Ask your assistant: > "What x02 tools do you have?" You are looking for **fifteen**. If it lists them, you are connected. The quickest real test: > "Upload https://x02.me/favicon.ico to my account and give me the link." That exercises the connection, the permissions and an actual write, all in one. --- ### If something is not working The three people hit most often are below. For the longer list — refused permissions, expired sign-ins, quota errors and the rest — see [Troubleshooting X02 MCP Connections](/blog/troubleshooting-x02-mcp-connections). **The connector is added, but the assistant has no tools.** Almost always an out-of-date list. Your app asks X02 what it can do once, when you first connect, and then remembers the answer. Disconnect it and connect again — closing and reopening the app is usually not enough. Do the same whenever X02 adds new tools, or you will keep seeing the old set. **It says it cannot upload a file I attached to the chat.** Expected, and not a misconfiguration. An assistant can *look* at an attachment but has no way to pass the bytes to a remote server — that is a gap in the protocol itself, not in X02. Ask it to upload anyway and it will hand you a drop link. Or paste files into your tray at [x02.me/drop](https://x02.me/drop) beforehand and they will be waiting. **Sign-in opens but nothing happens after I approve.** Two likely causes. Your browser may be blocking the jump back to the app — check for a blocked-popup warning. Or you took longer than ten minutes to approve, in which case the request has quietly expired. Either way, start the connection again from the app. One thing worth doing every time: before you click Allow, read the address the consent screen says it will send you back to. Anyone can build an app and name it whatever they like, including "X02 Official" — the name proves nothing, the address does. If it is not the app you just clicked in, click Deny. --- ## Part 2: The Developer & Power User Guide Editors and command-line tools, plus the things worth knowing when a connection misbehaves. --- ### Claude Code One command in your terminal: ```bash claude mcp add --transport http x02 https://up.x02.me/mcp ``` Your browser opens for sign-in, and you are done. If your shell says `claude: command not found`, the CLI is not installed — it is separate from the Claude desktop app: ```bash npm install -g @anthropic-ai/claude-code ``` Reopen your terminal afterwards so it picks up the new command. --- ### Cursor, Windsurf and VS Code All three support remote MCP servers. Look for **MCP** or **Model Context Protocol** in settings, add a new server, and give it the same URL. Some versions ask you to pick a transport — choose **HTTP** or **Streamable HTTP**, not stdio, since X02 is a hosted server rather than a local program. --- ### MCP Inspector, for when something breaks The official inspector is the fastest way to see what is actually happening, because it shows each step of the handshake instead of a generic failure: ```bash npx @modelcontextprotocol/inspector ``` Connect it to `https://up.x02.me/mcp`. If it works here but not in your assistant, the problem is the client's configuration, not X02 — which narrows things down considerably. --- ### More troubleshooting **The drop link expired before I got to it.** They stay open for fifteen minutes. Ask the assistant for a new one — or paste files into your tray at [x02.me/drop](https://x02.me/drop) ahead of time instead, where a drop waits twenty-four hours for whichever assistant asks next. **I approved the wrong app, or want to revoke access.** Open **Dashboard → Account → Connected apps** and click **Disconnect** beside it. That kills its tokens immediately, whatever the client does. The same list has **Edit permissions**, to take individual permissions away from an app you want to keep. Your API key, folder keys and every other connected app are unaffected — revocation is per app, which is the main reason to use MCP over handing out a key. Removing the connector in the client works too, but how *fast* depends on the client. Ones that call X02's revocation endpoint on disconnect kill the token immediately; ones that simply forget it leave the access token valid until it expires, which is at most an hour. If you need a connection dead right now, use Connected apps. **I use two assistants and one cannot see the other's file.** Working as intended. A file requested by one app is delivered only to that app. If you want something available to whichever assistant asks next, drop it into your tray at `/drop` instead of using a request link. --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **What MCP is and what it can do** | [Connect AI Assistants to X02 with MCP](/blog/connect-ai-assistants-to-x02-with-mcp) | | **Every failure mode, in one place** | [Troubleshooting X02 MCP Connections](/blog/troubleshooting-x02-mcp-connections) | | **What each permission actually grants** | [X02 MCP Permissions: What You Are Actually Approving](/blog/x02-mcp-permissions-and-scopes-explained) | | **Getting files in from a chat** | [How to Send Files to X02 From an AI Chat](/blog/how-to-send-files-to-x02-from-an-ai-chat) | | **Things worth asking for** | [Ten Things to Ask an Assistant Connected to X02](/blog/x02-mcp-workflows-and-recipes) | | **Upload files by every other method** | [How to Upload Files to X02](/blog/how-to-upload-files-to-x02) | | **Get an API key for your own scripts** | [How to Get an API Key](/blog/how-to-get-an-api-key) | | **Organize uploads into folders** | [How to Organize Files with Folders](/blog/how-to-organize-files-with-folders) | | **Full endpoint reference** | [API Documentation](/api-docs) | --- # Troubleshooting X02 MCP Connections Source: https://x02.me/blog/troubleshooting-x02-mcp-connections 2026-09-22 · X02 Team Most MCP failures surface as something unhelpfully vague — "the connector could not be reached", or an assistant that cheerfully claims it has no X02 tools. This page maps the symptom you can see to the cause underneath. > [!TIP] > **Quick Navigation:** > * [Start here: is it X02 or your client?](#start-here-is-it-x02-or-your-client) > * [The connection will not establish](#the-connection-will-not-establish) > * [Connected, but no tools](#connected-but-no-tools) > * [A tool refuses](#a-tool-refuses) > * [Uploads and file handover](#uploads-and-file-handover) > * [Things that look broken but are not](#things-that-look-broken-but-are-not) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## Start here: is it X02 or your client? Two commands settle it in under a minute. **Is the server up?** This should return JSON listing the six scopes: ```bash curl https://up.x02.me/.well-known/oauth-protected-resource/mcp ``` **Does the handshake work?** The official inspector walks each step and shows you where it stops, instead of collapsing everything into one failure: ```bash npx @modelcontextprotocol/inspector ``` Point it at `https://up.x02.me/mcp`. If the inspector connects and your assistant does not, the problem is the client's configuration — which narrows things down considerably, and is the answer more often than not. --- ## The connection will not establish **"Could not connect" / "server unreachable", and the URL is right.** Check the transport. Some clients ask you to choose one when adding a server — X02 needs **HTTP** or **Streamable HTTP**, not stdio. Stdio is for MCP servers that run as a local program on your machine; X02 is hosted, so there is no process to launch. **There is no "Add custom connector" option at all.** Plan or policy, depending on the client. | Client | Requirement | |---|---| | Claude, personal plans | Available on all plans | | Claude, Team and Enterprise | An organization owner adds it under Organization settings → Connectors; members then connect their own account | | ChatGPT | A paid plan (Plus, Pro, Business, Enterprise or Edu). Not on Free, and not in the mobile apps | | ChatGPT, Business and Enterprise | An admin may need to allow developer mode first | In ChatGPT the toggle is also one level deeper than most guides say: **Settings → Apps & Connectors → Advanced settings → Developer mode**. **Sign-in opens, but nothing happens after I approve.** Usually one of two things. Either the browser blocked the redirect back to the app, or the authorization request expired — they last **ten minutes**, and a stale one fails quietly rather than saying so. Start the connection again from the client and complete it promptly. **I am not sure the consent screen is legitimate.** Look at the redirect host it shows you. Any developer can register a client and name it anything, "X02 Official" included — the name is self-declared and proves nothing. The redirect destination is checked. If that host is not the app you just clicked in, deny it. --- ## Connected, but no tools **The connector shows as connected and the assistant says it has none.** Almost always a cached tool list. Clients fetch the tool list when they first connect and hold onto it. Disconnect the connector and reconnect it — restarting the app alone often is not enough. This is also what to do after X02 ships new tools. Your client keeps the list it fetched originally, so new capabilities stay invisible until it re-handshakes. **How to confirm it is actually working.** Ask: > "What x02 tools do you have?" You are looking for **fifteen**. Then the real test, which exercises the connection, the permissions and a write in one go: > "Upload https://x02.me/favicon.ico to my account and give me the link." --- ## A tool refuses **"This app was not granted the *files:delete* permission."** Exactly what it says: the connection was approved without that scope. Reconnect the app and approve it. Note that the message names the scope the *tool* needs, which is not always the one you would guess — deleting a folder needs `folders:write`, not `files:delete`. The full mapping is in [the permissions guide](/blog/x02-mcp-permissions-and-scopes-explained). **Everything worked, and now every call fails.** If this happened suddenly and reconnecting fixes it, the likely cause is refresh token reuse detection. X02 rotates refresh tokens, and if a retired one is presented again — two copies of a client running against the same stored credential, usually — the whole grant is revoked on the assumption the token was copied. Reconnecting issues a fresh one. **A 401 with a `WWW-Authenticate` header.** Normal, and not an error you need to act on. It is how the server tells a client where to find its metadata on the first request, before any token exists. If you are watching network traffic during setup you will see exactly one. --- ## Uploads and file handover **"I cannot upload the file you attached."** Expected. An assistant can look at an attachment but has no way to pass the bytes to a remote server — a gap in the protocol, not a misconfiguration. Ask it to upload anyway and it hands you a drop link. **The drop link expired.** They stay open **fifteen minutes**. Ask for another; nothing is lost. Better, paste files into your tray at [x02.me/drop](https://x02.me/drop) before you ask — a tray drop waits twenty-four hours and goes to whichever assistant asks next. **The assistant keeps saying "not dropped yet".** That is the expected in-between state, not a failure. It polls in roughly ten-second stretches, so a couple of "waiting" results while you find the file are normal. If several pass with nothing, check you actually opened the link — the assistant cannot tell the difference between a page you never opened and one you are still using. **I use two assistants and one cannot see the other's file.** Working as intended. A file handed over through a drop link is delivered to the app that requested it, and to no other. Use the tray if you want something available to whichever assistant asks next. **"Only image URLs are supported."** `upload_from_url` rehosts images only — the URL has to answer with an `image/*` content type. A PDF, video or HTML page is refused. Use a drop link for those instead. **The upload is refused as too large.** Two different ceilings, depending on the route. `upload_file` carries bytes inline through the conversation and refuses above **1 MB** (comfortable under 128 KB) — base64 inflates by a third, so even a modest screenshot exceeds it. Drop links and URL rehosting use your plan's real limit: 80 MB anonymous, 200 MB registered, 512 MB on Pro. **"You have reached your daily upload limit."** MCP shares your normal quota — 250 uploads a day on a registered account, 1,500 on Pro. Ask `get_account` before a batch: "how many uploads do I have left today?" --- ## Things that look broken but are not **The uploaded file has a name I did not choose.** X02 generates a short stored name on upload. That is the one every other tool expects. Your original name is kept alongside it and `list_files` searches both, so you can still find it by the name it had on your machine. **A search returned something unrelated.** Search is fuzzy by design. Results are labelled `[exact match]` or `[fuzzy match]`, and a fuzzy match may not contain your term at all. Ask the assistant to list what it found rather than trusting a single confident answer. **`view_file` says there is nothing to show.** It only previews images. PDFs, videos and archives have no thumbnail — `get_file` gives you their details instead. **Claude connects from Anthropic's servers, not your computer.** Invisible for X02, which is on the public internet. It matters if you ever add a *different* MCP server running on localhost or behind a company VPN — those cannot work this way, however well they run on your machine. **Uploads went into a folder I did not specify.** Your default upload folder applies to assistant uploads too. See [setting a default upload folder](/blog/how-to-set-a-default-upload-folder). --- ## Still stuck Email [contact@x02.me](mailto:contact@x02.me) or ask in [our Discord](https://discord.gg/qyBDzFWAaY). The two things worth including: which client you are using, and whether `npx @modelcontextprotocol/inspector` can connect to `https://up.x02.me/mcp`. That second answer tells us which half of the problem to look at. --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **Step-by-step setup for your client** | [Set Up X02 in Claude, ChatGPT, Cursor and More](/blog/set-up-x02-mcp-in-your-ai-client) | | **What MCP is and what it can do** | [Connect AI Assistants to X02 with MCP](/blog/connect-ai-assistants-to-x02-with-mcp) | | **What each permission grants** | [X02 MCP Permissions: What You Are Actually Approving](/blog/x02-mcp-permissions-and-scopes-explained) | | **Getting files in from a chat** | [How to Send Files to X02 From an AI Chat](/blog/how-to-send-files-to-x02-from-an-ai-chat) | | **Things to ask your assistant** | [Ten Things to Ask an Assistant Connected to X02](/blog/x02-mcp-workflows-and-recipes) | | **Full endpoint reference** | [API Documentation](/api-docs) | --- # X02 MCP Permissions: What You Are Actually Approving Source: https://x02.me/blog/x02-mcp-permissions-and-scopes-explained 2026-09-22 · X02 Team When you connect an AI assistant to X02, a consent screen lists the permissions it is asking for — up to six — each with a checkbox, and asks you to approve. Most people skim it. This page is the long version — what each scope governs, where it is enforced, and what is left standing if a token leaks. > [!TIP] > **Quick Navigation:** > * [The six scopes](#the-six-scopes) > * [Which scope governs which tool](#which-scope-governs-which-tool) > * [Where scopes are enforced](#where-scopes-are-enforced) > * [How long a connection lasts](#how-long-a-connection-lasts) > * [What a stolen token can and cannot do](#what-a-stolen-token-can-and-cannot-do) > * [Revoking access](#revoking-access) > * [What to withhold](#what-to-withhold) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## The six scopes That is the entire vocabulary. Anything an app asks for outside this list is rejected before you ever see a consent screen. | Scope | Grants | |---|---| | `account:read` | Your username, plan, storage use and remaining daily uploads | | `files:read` | List files, inspect one, and view an image thumbnail | | `files:write` | Upload, rename, move, and receive files you hand over | | `files:delete` | Permanently delete a file | | `folders:read` | List your folders | | `folders:write` | Create, delete and share folders | You can check what any connected app actually holds without leaving the chat. Ask it: > "What X02 permissions do you have?" `get_account` returns the granted scope list alongside your plan and usage, so the answer comes from the token itself rather than from the app's memory of what it asked for. --- ## Which scope governs which tool The mapping is one-to-one and fixed. No tool consults more than one scope, and none is ungoverned. | Scope | Tools | |---|---| | `account:read` | `get_account` | | `files:read` | `list_files`, `get_file`, `view_file` | | `files:write` | `upload_file`, `upload_from_url`, `rename_files`, `move_files`, `request_file_from_user`, `check_file_handoff` | | `files:delete` | `delete_files` | | `folders:read` | `list_folders` | | `folders:write` | `create_folder`, `delete_folder`, `share_folder` | Three of these pairings surprise people. **Receiving a file needs `files:write`.** `request_file_from_user` and `check_file_handoff` are how an attachment reaches X02 — the assistant asks, you drop the file, it collects the result. That is an upload, so it sits under the same scope as one. **Deleting a folder is `folders:write`, not `files:delete`.** Creating, deleting and sharing folders are a single permission. An app you deliberately denied `files:delete` can still delete a *folder* if it holds `folders:write`. Nothing inside is destroyed — the files return to your root library — but the folder and its share link are gone. **Sharing a folder is also `folders:write`.** `share_folder` publishes an album at a public link that anyone holding it can open. If you would rather an assistant never be able to publish anything, that is the scope to withhold. --- ## Where scopes are enforced Not at sign-in. On every call. The bearer check that guards the `/mcp` endpoint can only see that a token is valid — it cannot know which tool a JSON-RPC body is about to invoke. So each tool asserts its own scope before it does anything, and refuses with a message naming the missing permission: ``` This app was not granted the "files:delete" permission. Reconnect it and approve that scope to continue. ``` This matters because of what sits behind the server. X02's MCP server talks to the same REST API your API key does, and an API key has no concept of scopes — it can do everything. The per-call assertion is the only thing standing between a narrowly-scoped token and full account access. It is the first thing that runs in every tool, before any request leaves the server. ![Architecture Diagram](/blog-diagrams/x02-mcp-permissions-and-scopes-explained-1.svg) --- ## How long a connection lasts | | Lifetime | What it does | |---|---|---| | **Authorization request** | 10 minutes | The pending approval on the consent screen. Sit on it too long and it fails quietly | | **Access token** | 1 hour | What the app sends on each tool call | | **Refresh token** | 30 days, rotating | Lets the app mint a new access token without asking you again | The refresh token rotates: each use issues a new one and retires the old. If a retired refresh token is ever presented again, X02 treats it as a sign the token was copied and **revokes the entire grant** — that app is disconnected outright and has to be re-approved. It is a blunt response, deliberately, because the alternative is quietly letting two parties share one connection. Scope cannot grow on refresh either. An app that was granted `files:read` cannot ask for `files:delete` at renewal time; the request is refused rather than silently downgraded. Widening permissions always means a new consent screen with your name on it. --- ## What a stolen token can and cannot do Worth being concrete, because "OAuth is safer than an API key" is easy to assert and harder to justify. **Your API key never reaches the app.** The server resolves your account from the app's token internally, server-side. There is no call an app can make that returns your key, and a leaked token cannot be exchanged for one. **Tokens are bound to one resource.** They are minted for `https://up.x02.me/mcp` specifically. Present one anywhere else and it is refused — a token lifted from one service cannot be replayed against another. **Tokens are bound to one app.** Approving Claude does not produce a credential Cursor can use. Each connection is its own grant with its own scopes. **The blast radius is the scopes you approved, for at most an hour.** Compare that to an API key: full account access, no expiry, and revoking it means rotating the key and breaking every other integration you own. What it *can* do, within its scopes, is everything you can. A token with `files:delete` deletes files for real; X02 has no trash can. Scopes limit reach, not judgement. --- ## Revoking access Open **Dashboard → Account → Connected apps**. Every app you have approved is listed there, with the address it sends you back to, when you connected it and when it last made a request. Two buttons matter: **Disconnect** revokes the app's access token and refresh token on X02's side, at once, whatever the client does. It does not depend on the app being well-behaved. The app has to ask again, and you have to approve again, before it can touch your account. **Edit permissions** lets you take individual scopes away from an app that stays connected. The change bites on the app's next request — X02 clips the tokens it has already issued rather than waiting for them to expire. It only removes: to give an app *more* than it holds, reconnect it and approve the larger set on the consent screen, so you always see the request being made. You can also remove the connector in the app itself. What happens next depends on the client: ones that call X02's revocation endpoint on disconnect kill the token there and then, while ones that simply forget the credential leave the access token valid until it expires — at most an hour. There is no way for us to tell from the outside which kind you have, which is why the Connected apps page is the reliable route. What is guaranteed either way: revoking one app touches nothing else. Your API key, your folder keys and every other connected assistant carry on unaffected. That is the property an API key cannot give you, and it is the real argument for MCP. --- ## Reading the consent screen Two things on that page are worth a second of attention. **The app's name is self-declared.** Any developer can register a client and call it whatever they like, "X02 Official" included. Registration is open by design — it is what lets a client connect with nothing but a URL — so the name proves nothing. **The redirect host is not.** The consent screen shows the host you will be sent back to after approving. That one is checked. If it is not the app you just clicked in, deny it and find out why. For assistants X02 knows — starting with Claude — the screen goes one step further. When the address is that app's own callback, it says **Recognised app**, and the name in the heading comes from X02 rather than from the app. For anything else, including an app that merely *calls* itself Claude, you get the warning that X02 has not vetted it, with its real address beside it. Recognition is by address, not name, so a convincing name earns nothing. It is also a narrower statement than it sounds. It says where the result will be sent, not who started the request. If you did not just start this connection from that app, deny it. --- ## What to withhold A rough guide, from least to most trusting: | If you want | Approve | Withhold | |---|---|---| | A read-only assistant that can find things | `account:read`, `files:read`, `folders:read` | everything else | | The same, plus uploading and tidying | add `files:write`, `folders:write` | `files:delete` | | Full control, including cleanup | all six | — | Most people want the middle row. It covers every ordinary request — upload this, find that, make a folder, move these into it, share the album — and the only thing it cannot do is destroy a file. You do not need the client's cooperation to get the narrower connection. Whatever an app asks for, X02's consent screen puts a checkbox beside each permission: untick the ones you would rather withhold and the button reads, for example, **Allow 4 of 6**. The app is then told exactly which permissions it received, and anything outside them is refused on every call. You can trim further later under **Connected apps**, but not widen — an app that wants more has to ask again. --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **What MCP is and what it can do** | [Connect AI Assistants to X02 with MCP](/blog/connect-ai-assistants-to-x02-with-mcp) | | **Step-by-step setup for your client** | [Set Up X02 in Claude, ChatGPT, Cursor and More](/blog/set-up-x02-mcp-in-your-ai-client) | | **Getting files into X02 from a chat** | [How to Send Files to X02 From an AI Chat](/blog/how-to-send-files-to-x02-from-an-ai-chat) | | **When a connection misbehaves** | [Troubleshooting X02 MCP Connections](/blog/troubleshooting-x02-mcp-connections) | | **Get an API key for your own scripts** | [How to Get an API Key](/blog/how-to-get-an-api-key) | | **Full endpoint reference** | [API Documentation](/api-docs) | --- # Ten Things to Ask an Assistant Connected to X02 Source: https://x02.me/blog/x02-mcp-workflows-and-recipes 2026-09-22 · X02 Team Once X02 is connected, the tools disappear. You do not call `move_files` — you say "put this week's screenshots in a folder" and something sensible happens. The gap between those two sentences is where people get stuck: knowing the tools exist does not tell you what is worth asking for. These are ten requests that work well, and the reason each one does. > [!TIP] > **Quick Navigation:** > * [Finding things](#finding-things) > * [Tidying up](#tidying-up) > * [Sharing](#sharing) > * [Batch jobs](#batch-jobs) > * [Where it struggles](#where-it-struggles) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## Finding things ### 1. "Find the invoice PDF I uploaded last month" > Why it works: search matches the **original** name, not just the generated one. This is the single most useful thing the connection does, and it is not obvious. X02 renames files on upload — `invoice-march.pdf` becomes something like `aB3xY9.pdf` — but it keeps your original name alongside it, and search covers both. So you can ask for a file by the name it had on your machine, months later, without ever having seen the short one. Search is fuzzy, which cuts both ways. Results come back labelled `[exact match]` or `[fuzzy match]`, and a fuzzy match may not contain your search term at all. A well-behaved assistant will tell you which it found rather than presenting a near-miss as certain. If it hands you one confident answer to a vague query, ask it to list what it found. ### 2. "What is aB3xY9.png?" > Why it works: `view_file` returns the picture, not the metadata. A generated filename tells you nothing, and it tells the assistant nothing either. `view_file` hands it a downscaled thumbnail so it can actually look — useful before acting on something, and the difference between "I found a file called aB3xY9.png" and "that is the pricing table screenshot." It returns a thumbnail, not the original, so it is enough to recognise a screenshot but not to read fine print off one. Images only: PDFs, videos and archives have no preview. ### 3. "What are my ten largest files?" > Why it works: `list_files` sorts by size, name or date, and narrows by file kind and upload date before it sorts. Straightforward, and the quickest way to work out where your storage went. "What are my oldest files?" works the same way, which is a better question than it looks if you are trying to decide what to keep. --- ## Tidying up ### 4. "Make a folder called Release v3 and move everything from this week into it" > Why it works: two tools, one sentence. `create_folder` then `move_files`. The move takes a list, so it is one call rather than thirty, and it **partially succeeds by design** — if three files out of thirty fail, you get twenty-seven moved and three named, not a silent rollback. Ask what failed if the count does not match. ### 5. "Rename these to something I can actually read" > Why it works: renaming does not break the link. Worth being precise about what happens here, because it is the most common misunderstanding about X02. Renaming changes the stored filename, and the public URL contains the stored filename — so the URL changes with it. If you have already pasted a link somewhere, renaming breaks it. If what you actually want is to swap the file behind a link that is already in circulation, that is a different operation: see [replacing a file without changing its link](/blog/how-to-replace-file-without-changing-link). ### 6. "Move everything out of the Screenshots folder and delete it" > Why it works: deleting a folder does not delete its files. Files in a deleted folder return to your root library. So the "move everything out" half is unnecessary — deleting the folder does it for you. Worth knowing before you ask an assistant to do the long version. Deleting a *file* is another matter entirely. There is no trash can, the URL stops working immediately, and it cannot be undone. Both delete tools are marked destructive so your client can prompt you first; if yours does, read the prompt. --- ## Sharing ### 7. "Share the Screenshots folder and send me the album link" > Why it works: `share_folder` publishes and returns the link in one step. The folder becomes a public album that anyone holding the link can open. There is no sign-in and no access list — the link *is* the permission, so treat it like a password. Two follow-ups worth knowing exist: "stop sharing that folder" unpublishes it, and "give that album a new link" regenerates it, which invalidates the old one. The second is what you want when a link has spread further than you intended. ### 8. "Upload this and make it expire in a week" > Why it works: both upload tools take an expiry. `1h`, `6h`, `1d`, `7d` or `30d`. After that the file deletes itself. Good for anything you are sending to one person for one purpose — a screenshot in a support thread, a build someone needs today. Omit it and the file is permanent. --- ## Batch jobs ### 9. "How much of today's upload quota do I have left?" > Why it works: `get_account` reads live usage, not a cached number. Ask this *before* a batch, not after it fails halfway. It returns your plan, uploads used and remaining today, storage used against your limit, and the largest single file your plan allows. MCP is not a separate allowance — the same daily limit applies whether you upload through an assistant, the website or ShareX: 250 a day on a registered account, 1,500 on Pro. ### 10. "Rehost all of these image URLs on my account" > Why it works: the bytes never pass through the conversation. Give it a list of URLs and it works through them. This is the one genuinely high-volume thing the connection does well, because `upload_from_url` fetches server-side — nothing is base64-encoded into the chat, so there is no context cost per file and no size ceiling beyond your plan. Images only, though. A list of PDF links will fail on every row. --- ## Where it struggles **Attachments in the chat.** An assistant can look at a file you attached but cannot hand the bytes to X02 — no client exposes attachments as URLs. Ask anyway and it gives you a drop link, or pick them up from your tray at [x02.me/drop](https://x02.me/drop) if you dropped them beforehand. Full detail in [how to send files to X02 from a chat](/blog/how-to-send-files-to-x02-from-an-ai-chat). **"Delete everything from before June."** There is no bulk delete and no date filter on deletion — it would have to list, then delete one file at a time, and there is no undo if it misreads your intent. If you genuinely want that, do it in the dashboard where you can see what you are selecting. **Anything about a file's contents beyond an image.** It can see a thumbnail. It cannot read your PDF, transcribe your video or search inside a document. `get_file` gives it the URL, size, upload date, kind, folder and privacy — everything except what is inside. **Remembering the connection.** A new conversation starts with no memory of the last one. "The file from yesterday" means nothing; "the invoice PDF" means something, because it can search for that. --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **What MCP is and what it can do** | [Connect AI Assistants to X02 with MCP](/blog/connect-ai-assistants-to-x02-with-mcp) | | **Step-by-step setup for your client** | [Set Up X02 in Claude, ChatGPT, Cursor and More](/blog/set-up-x02-mcp-in-your-ai-client) | | **Getting files in from a chat** | [How to Send Files to X02 From an AI Chat](/blog/how-to-send-files-to-x02-from-an-ai-chat) | | **What each permission grants** | [X02 MCP Permissions: What You Are Actually Approving](/blog/x02-mcp-permissions-and-scopes-explained) | | **Organize uploads into folders** | [How to Organize Files with Folders](/blog/how-to-organize-files-with-folders) | | **Replace a file without changing its link** | [How to Replace a File Without Changing the Link](/blog/how-to-replace-file-without-changing-link) | --- # Connect AI Assistants to X02 with MCP Source: https://x02.me/blog/connect-ai-assistants-to-x02-with-mcp 2026-09-21 · X02 Team You can now let an AI assistant work on your X02 files for you — find an old upload, look at an image, add a new file, tidy things into folders, hand you a share link. You ask; it does the clicking. It works because X02 speaks **[the Model Context Protocol](https://modelcontextprotocol.io)** (MCP), the open standard AI apps use to connect to outside services. In practice that means you paste one address into Claude, ChatGPT or Cursor, sign in to X02 once in your browser, and you are done. You never copy an API key into the AI app. **Just want it working?** [Set Up X02 in Claude, ChatGPT, Cursor and More](/blog/set-up-x02-mcp-in-your-ai-client) is the step-by-step for each app. This page is the *why* — what an assistant can reach, what it cannot, and what happens to your credentials. > [!TIP] > **Quick Navigation:** > * [What This Actually Gets You](#what-this-actually-gets-you) > * [Connecting a Client](#connecting-a-client) > * [What the Assistant Can Do](#what-the-assistant-can-do) > * [Permissions and Scopes](#permissions-and-scopes) > * [How Sign-In Works](#how-sign-in-works) > * [Disconnecting an App](#disconnecting-an-app) > * [Limits and Gotchas](#limits-and-gotchas) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## What This Actually Gets You The REST API already lets you script X02. MCP is for the other case: when an AI assistant is doing the work and you want it to reach your account **safely**. The difference that matters is the credential. An API key is all-or-nothing — handing one to a third-party app gives it everything your account can do, forever, and taking it back means rotating the key and breaking every other integration you own. MCP uses OAuth instead: | | API Key | MCP (OAuth) | |---|---|---| | **What the app holds** | Your full account key | Its own token, scoped to what you approved | | **Where you type it** | Into the app | Nowhere — you sign in on x02.me | | **Limiting what it can do** | Not possible | Per-scope, shown before you approve | | **Revoking one app** | Rotate the key, break everything else | Disconnect that app only | Use the API key for your own code. Use MCP for software you did not write. --- ## Connecting a Client The server lives at **`https://up.x02.me/mcp`**. ### Claude Code ```bash claude mcp add --transport http x02 https://up.x02.me/mcp ``` ### Claude Desktop, Cursor, Windsurf, and others Add a remote MCP server in the client's settings and give it the same URL: ``` https://up.x02.me/mcp ``` ### Trying it by hand The official MCP Inspector is useful for browsing the tools and calling them manually: ```bash npx @modelcontextprotocol/inspector ``` Whichever client you use, it will open a browser window the first time you connect. Sign in to X02, review what the app is asking for, and approve. The client stores its own token from there. ![Architecture Diagram](/blog-diagrams/connect-ai-assistants-to-x02-with-mcp-1.svg) --- ## What the Assistant Can Do Fifteen tools, covering the things you would otherwise do in the dashboard: | Tool | What it does | |---|---| | `get_account` | Your plan, storage use, 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` | URL, size, upload date, kind, folder, and whether it is private, for one file | | `view_file` | Shows the assistant a downscaled thumbnail of an image, so it can identify a file instead of guessing. Images only | | `request_file_from_user` | Gives you a link to drop a file into — how an attachment reaches X02 | | `check_file_handoff` | Collects what you dropped, waiting for the upload so you never have to say "done" | | `upload_file` | Upload small file content directly (see the size note below) | | `upload_from_url` | Rehost an image that is already online, by URL. Images only | | `rename_files` | Rename one or more stored files | | `move_files` | Move one or many files into a folder, or back to the root library | | `delete_files` | Permanently delete one or more files | | `list_folders` | Folders with file counts and share status | | `create_folder` | Create a folder | | `delete_folder` | Delete a folder. Files inside return to the root library rather than being deleted | | `share_folder` | Publish a folder as a shared album and return the link, or stop sharing it | In practice you do not call these by name. You ask: > "Upload this screenshot and give me the link." > "Find the invoice PDF I uploaded last month." > "Make a folder called Release v3 and move everything from this week into it." > "Share the Screenshots folder and send me the album link." --- ## Permissions and Scopes Every connection is scoped. The consent screen lists exactly what the app asked for before you approve it: | Scope | Grants | |---|---| | `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 | These are enforced on every single call, not just at sign-in. An app that was granted `files:read` and later tries to delete something gets refused — the request never reaches your files. > [!NOTE] > **Deleting is permanent.** `delete_files` and `delete_folder` are marked destructive so clients can prompt you first, but X02 has no trash can. > > Note that the two are gated separately. `files:delete` covers `delete_files`; deleting a *folder* is part of `folders:write`, alongside creating and sharing them. Withholding `files:delete` stops an app removing your files, but it can still delete a folder if it holds `folders:write` — the files inside return to your root library rather than being destroyed, so nothing is lost, but the folder is gone. --- ## How Sign-In Works Worth understanding, because it is the part that protects you. 1. The client discovers the server's capabilities from a metadata document — no configuration beyond the URL. 2. It registers itself and sends your browser to X02 with a cryptographic challenge (PKCE). 3. You sign in on **x02.me**, not in the app, and see what is being requested. 4. On approval, X02 issues an access token bound to that one app and those scopes. It lasts an hour, alongside a refresh token the client rotates to stay connected for up to thirty days without asking you again. Two details that matter: **The consent screen shows where you will be sent back to.** Any developer can register a client and name it anything — "X02 Official" included. The name is self-declared; the redirect destination is not. If that host is not the app you just clicked in, deny it. **Your API key never leaves X02.** The server resolves your account from the app's token internally. The app cannot read your key, and a leaked token cannot be turned into one. --- ## Disconnecting an App Revoking is per app, and this is the part worth reading properly. The reliable way is on X02's side: open **Dashboard → Account → Connected apps**, find the app and click **Disconnect**. That revokes its access token and refresh token immediately, whatever the client does. The same list lets you **Edit permissions** to take individual scopes away from an app that stays connected. Removing the connector in your client also works, but how fast depends on the client. Well-behaved ones call X02's revocation endpoint on the way out, which kills those tokens on our side at the same moment. Not every client does, and there is no way for us to tell from the outside which one you are using. So, to be precise about what you are guaranteed: - **Your API key, your folder keys and every other connected app are unaffected.** That part is absolute — this is the whole reason to prefer MCP over handing out a key. - **Disconnecting from Connected apps ends access immediately.** - **If you only remove the connector in the client and it does not revoke,** the access token it already holds keeps working until it expires — at most an hour — and its refresh token would remain valid for thirty days. Disconnect from Connected apps as well if that matters. --- ## Limits and Gotchas **Sending a file you have attached to the chat.** The assistant can *look* at an attachment but cannot hand the bytes to X02 — there is no mechanism in the protocol for it, and no AI client exposes your attachments as public links (which is just as well). So instead: Ask it to upload something, and it gives you a link like `x02.me/drop/K7M2QXR4`. Open it, press **Ctrl+V** if the file is already on your clipboard, and the assistant picks it up and carries on — you never have to go back and tell it you are done. Drop links stay open for fifteen minutes; if you wander off and come back to an expired one, just ask for another. Better still, **pin `x02.me/drop`** as a tab. Anything you paste in there waits in your tray for twenty-four hours, so if you drop the screenshot *before* you ask, there is no link and no waiting at all. `upload_file` still exists for genuinely small content — icons, SVGs, short text files. It is comfortable under 128 KB and refuses outright above 1 MB, which a single screenshot already exceeds once base64 has inflated it by a third. When it refuses, it suggests the two routes that do work: upload it yourself on x02.me and tell the assistant what it was called — X02 keeps your original name alongside the short one and `list_files` searches both — or hand over a URL. Ask for a drop link and it will give you one of those too. `upload_from_url` is the fastest route for anything already online: give the assistant the URL and the bytes never touch the chat. It rehosts **images only** — the URL has to answer with an `image/*` content type, so a PDF or a video link is refused. Private and internal hostnames are refused as well. **The stored filename is not your filename.** X02 assigns a short name on upload. Assistants are told to use the returned filename for anything afterwards, but if you are cross-referencing by hand, check `list_files` rather than assuming. **Uploads count against your normal quota.** MCP is not a separate allowance — same daily limits, same plan file-size ceilings. Ask for `get_account` if you are about to run a big batch. **Uploads land in your default folder.** If you have set one, that is where assistant uploads go too. See [setting a default upload folder](/blog/how-to-set-a-default-upload-folder). --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **Step-by-step setup for your client** | [Set Up X02 in Claude, ChatGPT, Cursor and More](/blog/set-up-x02-mcp-in-your-ai-client) | | **What each permission actually grants** | [X02 MCP Permissions: What You Are Actually Approving](/blog/x02-mcp-permissions-and-scopes-explained) | | **Getting files in from a chat** | [How to Send Files to X02 From an AI Chat](/blog/how-to-send-files-to-x02-from-an-ai-chat) | | **Things worth asking for** | [Ten Things to Ask an Assistant Connected to X02](/blog/x02-mcp-workflows-and-recipes) | | **When something breaks** | [Troubleshooting X02 MCP Connections](/blog/troubleshooting-x02-mcp-connections) | | **Upload files by every other method** | [How to Upload Files to X02](/blog/how-to-upload-files-to-x02) | | **Get an API key for your own scripts** | [How to Get an API Key](/blog/how-to-get-an-api-key) | | **Organize uploads into folders** | [How to Organize Files with Folders](/blog/how-to-organize-files-with-folders) | | **Set a default upload folder** | [How to Set a Default Upload Folder](/blog/how-to-set-a-default-upload-folder) | | **Full endpoint reference** | [API Documentation](/api-docs) | --- # All X02 Features and Capabilities Source: https://x02.me/blog/all-features-and-capabilities 2026-09-01 · X02 Team X02 is a high-performance file hosting, media delivery, and developer automation platform. Whether you are an everyday user looking for fast clipboard screenshot sharing or a developer deploying CI/CD build drops, this directory outlines every core feature available across the platform. > [!TIP] > **Quick Feature Navigation:** > * [1. File Uploads & Ingestion Methods](#1-file-uploads--ingestion-methods) > * [2. File & Library Organization](#2-file--library-organization) > * [3. Custom Branding & Domain URLs](#3-custom-branding--domain-urls) > * [4. Security, Privacy & Access Control](#4-security-privacy--access-control) > * [5. Developer API & Integrations](#5-developer-api--automation) > * [6. Discord Community Infrastructure](#6-discord-community-infrastructure) > * [7. Real-Time AI Safety & Moderation](#7-real-time-ai-safety--moderation) > * [Feature Tier Comparison (Free vs Pro)](#feature-matrix-free-vs-pro) --- ## 1. File Uploads & Ingestion Methods X02 supports instant file ingestion across multiple channels without requiring complex configurations: | Feature | Description | Related Guide | |---|---|---| | **Web Drag & Drop** | Drag files from your desktop directly onto the homepage or dashboard. | [How to Upload Files ](/blog/how-to-upload-files-to-x02) | | **Clipboard Paste (`Ctrl+V`)** | Copy any image or screenshot to your clipboard and paste anywhere on the site for instant upload. | [How to Upload Files ](/blog/how-to-upload-files-to-x02) | | **ShareX One-Click Integration** | Capture desktop screenshots and upload automatically using personal `.sxcu` profiles. | [How to Integrate ShareX ](/blog/how-to-integrate-sharex-with-x02) | | **MCP Server (AI assistants)** | Connect Claude, ChatGPT, Cursor or any MCP client and have it upload, find and organize files for you, with scoped, revocable access and no API key pasted into the app. | [Connect AI Assistants ](/blog/connect-ai-assistants-to-x02-with-mcp) | | **File Handoff & Drop Tray** | Hand an assistant a file it cannot otherwise reach: it gives you a drop link, or you paste into your standing tray at x02.me/drop and it collects from there. | [Set Up Your AI Client ](/blog/set-up-x02-mcp-in-your-ai-client) | | **Custom Expiration Timers** | Set files to auto-delete after 10 minutes, 1 hour, 1 day, 7 days, 30 days, or persist permanently. | [How to Upload Files ](/blog/how-to-upload-files-to-x02) | | **Wide Format Support** | Images (PNG, JPG, WebP, GIF, SVG), Videos (MP4, WebM), Documents (PDF), and Archives (ZIP, TAR). | [How to Upload Files ](/blog/how-to-upload-files-to-x02) | --- ## 2. File & Library Organization Keep your media organized and maintainable over time: | Feature | Description | Related Guide | |---|---|---| | **Custom Folders** | Create colored folders with custom icons to organize projects, client work, and personal drops. | [How to Organize with Folders ](/blog/how-to-organize-files-with-folders) | | **Batch File Movement** | Move multiple files at once between folders or return them to your root gallery. | [How to Organize with Folders ](/blog/how-to-organize-files-with-folders) | | **Default Upload Destination** | Designate any folder so new web, clipboard, and ShareX uploads land there automatically. | [How to Set Default Folder ](/blog/how-to-set-a-default-upload-folder) | | **In-Place File Replacement** | Swap out file content (e.g. updating an outdated diagram) while preserving the exact URL, folder and privacy settings. | [How to Replace a File ](/blog/how-to-replace-file-without-changing-link) | | **File Renaming** | Change file names and extensions with automatic character sanitization and link preservation. | [How to Rename Files ](/blog/how-to-rename-uploaded-files) | --- ## 3. Custom Branding & Domain URLs Choose how your links are presented to viewers: | Feature | Description | Related Guide | |---|---|---| | **Short URLs (`x02.me/u/...`)** | Ultra-compact, minimalist links ideal for character-constrained social posts and chat drops. | [How to Switch URL Formats ](/blog/how-to-switch-between-short-urls-and-subdomains) | | **Personal Subdomains (`user.x02.me/i/...`)** | Branded URLs featuring your unique username. | [How to Switch URL Formats ](/blog/how-to-switch-between-short-urls-and-subdomains) | | **Custom Domain (Pro)** | Serve every upload from a domain you own (e.g. `img.yourbrand.com`) with automated DNS verification and free managed SSL. | [Custom Domain Setup Guide ](/blog/branded-file-hosting-custom-subdomains) | | **Wildcard Edge SSL** | Automatic TLS encryption for all tenant subdomains with zero certificate setup. | [Branded Subdomains Guide ](/blog/branded-file-hosting-custom-subdomains) | | **URL Format Toggle** | Switch your default upload URL style in one click via dashboard settings or REST API. | [How to Switch URL Formats ](/blog/how-to-switch-between-short-urls-and-subdomains) | --- ## 4. Security, Privacy & Access Control Protect your uploads and control who can view your media: | Feature | Description | Related Guide | |---|---|---| | **Account Connections** | 1-click social sign-in and account recovery via Discord and Google OAuth. | [How to Connect Accounts ](/blog/how-to-connect-discord-and-google-accounts) | | **Folder Password Protection** | Lock sensitive folders behind custom passwords for confidential client sharing. | [How to Organize with Folders ](/blog/how-to-organize-files-with-folders) | | **Folder Role Collaboration** | Invite team members with Admin, Editor, or Viewer roles. | [How to Organize with Folders ](/blog/how-to-organize-files-with-folders) | | **Instant Key Revocation** | Regenerate compromised API tokens immediately from your dashboard with zero downtime. | [How to Get an API Key ](/blog/how-to-get-an-api-key) | --- ## 5. Developer API & Automation Build integrations, bots, and CI/CD uploaders: | Feature | Description | Related Guide | |---|---|---| | **REST API** | Full programmatic control over uploads, replacements, renames, and folder organization. | [API Documentation ](/api-docs) | | **Personal API Keys** | Secret tokens for authenticating HTTP requests and developer scripts. | [How to Get an API Key ](/blog/how-to-get-an-api-key) | | **Folder-Specific API Keys** | Unique API keys hardwired to route uploads directly into specific folders. | [How to Organize with Folders ](/blog/how-to-organize-files-with-folders) | | **Headless Key Generation** | Programmatic onboarding via `POST /api/keys/generate`. | [How to Get an API Key ](/blog/how-to-get-an-api-key) | --- ## 6. Discord Community Infrastructure Turnkey tools for Discord server communities: | Feature | Description | Related Guide | |---|---|---| | **Discord Ticket System** | Web dashboard to configure interactive ticket panels, staff buttons, and modal questionnaires. | [Discord Tickets Dashboard ](/discord-tickets) | | **HTML Transcripts** | Permanent, searchable ticket archive transcripts delivered over high-speed edge CDN. | [Discord Infrastructure Guide ](/blog/building-discord-ticket-infrastructure) | | **Permanent Media Bridge** | Protect community media attachments from Discord CDN link expiration. | [Discord CDN Expiration Guide ](/blog/discord-cdn-expiration-permanent-media) | --- ## 7. Real-Time AI Safety & Moderation Enterprise-grade automated trust and safety: | Feature | Description | Related Guide | |---|---|---| | **Automated NSFW & CSAM Scanning** | Computer vision pipelines scan uploaded media asynchronously in milliseconds. | [AI Moderation Architecture ](/blog/realtime-nsfw-detection-at-scale) | | **Automated Quarantine System** | Flagged content is isolated instantly from public routing. | [System Scan ](/system-scan) | --- ## Feature Matrix (Free vs Pro) | Capability | Free Tier | Pro Plan | |---|---|---| | **Max File Upload Size** | 200 MB | 512 MB | | **Daily Upload Quota** | 250 files / day | 1,500 files / day | | **Custom Folders** | Up to 10 folders | Unlimited folders | | **Folder Password Protection** | Basic | Advanced encryption | | **Dedicated Folder API Keys** | Standard | Unlimited | | **Subdomain URL Branding** | Included | Included | | **Custom Domain (Bring Your Own Domain)** | Not available | Included | | **File Replacement & Renaming** | Included | Included | | **ShareX Integration** | Included | Included | | **Discord Ticket System** | Included | Included | --- ## Next Steps & Quick Links | Feature to Explore | Action Link | | :--- | :--- | | **Upload your first file** | [Open Homepage ](/) | | **Manage your uploads & folders** | [Open Dashboard ](/dashboard) | | **Explore developer documentation** | [View API Docs ](/api-docs) | | **Browse all step-by-step tutorials** | [Read All Guides ](/blog) | --- # How to Connect Discord and Google Accounts to X02 Source: https://x02.me/blog/how-to-connect-discord-and-google-accounts 2026-09-01 · X02 Team Managing passwords across dozens of services is tedious. To make accessing your files and media library fast and effortless, **X02 supports Account Connections for Discord and Google**. By linking your Discord or Google accounts, you unlock: * **1-Click Social Login:** Sign in instantly with one click on any browser or phone without typing your password. * **Account Recovery:** Never get locked out if you forget your local password. * **Discord Bot & Ticket Integration:** Seamlessly sync your Discord identity for server ticket moderation and bot management. * **Flexible Multi-Auth:** Connect multiple login methods to a single unified dashboard and disconnect them anytime. > [!TIP] > **Quick Navigation:** > * [How to Link Discord](#linking-your-discord-account) > * [How to Link Google](#linking-your-google-account) > * [How to Disconnect Accounts](#how-to-disconnect-a-linked-account) > * [OAuth 2.0 Security & Architecture (For Devs)](#part-2-the-developer--power-user-guide) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## Part 1: The Easy Guide (For Everyone) Linking your social accounts takes less than 10 seconds directly from your dashboard. ![Architecture Diagram](/blog-diagrams/how-to-connect-discord-and-google-accounts-1.svg) --- ### Linking Your Discord Account | Where to Go | Direct Link | Action | | :--- | :--- | :--- | | **`Dashboard`** **`Settings`** **`Account Connections`** | [Open Dashboard Settings](https://x02.me/dashboard) | Click **Connect Discord** | 1. **Log in** to your account at **[x02.me](https://x02.me)** and open your **Dashboard**. 2. Navigate to **Settings** and find the **Account Connections** section. 3. Next to **Discord**, click **Connect Discord**. 4. A secure Discord authorization window will appear. Click **Authorize**. Once approved, you will see a green **Linked** badge. You can now use the **Continue with Discord** button on the login screen anytime. --- ### Linking Your Google Account 1. Go to **Dashboard** **Settings** **Account Connections**. 2. Next to **Google**, click **Connect Google**. 3. Choose your Google account in the secure popup. 4. Confirm authorization. Your Google account is now linked, enabling 1-click **Continue with Google** sign-in. --- ### How to Disconnect a Linked Account If you want to unlink a Discord or Google profile from your X02 account: 1. Open **Dashboard** **Settings** **Account Connections**. 2. Click **Disconnect Discord** or **Disconnect Google**. 3. Confirm the action. > [!NOTE] > **Your Files Are Always Safe:** Disconnecting a social account never deletes your uploaded files, custom subdomain settings, or API keys. You can always reconnect at any time or log in using your standard username and password. --- ## Part 2: The Developer & Power User Guide For developers curious about how authentication and account linking are handled behind the scenes: --- ### 1. OAuth 2.0 Flow Under the Hood X02 follows the standard OAuth 2.0 authorization code grant flow: ![Architecture Diagram](/blog-diagrams/how-to-connect-discord-and-google-accounts-2.svg) 1. **CSRF State Verification:** When linking from the dashboard, a cryptographically random `state` parameter prevents cross-site request forgery attacks. 2. **Account Association:** The provider's unique ID (`discordId` or `googleId`) is securely stored in your user database record alongside verified email metadata. 3. **No Password Sharing:** X02 never accesses, sees, or stores your external Discord or Google passwords. --- ### 2. Disconnecting Accounts via REST API If you are managing user sessions programmatically: #### Disconnect Discord Endpoint: ```bash curl -X POST "https://x02.me/api/discord/disconnect" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" ``` #### Disconnect Google Endpoint: ```bash curl -X POST "https://x02.me/api/google/disconnect" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" ``` #### Successful JSON Response: ```json { "success": true, "message": "Account disconnected successfully" } ``` --- ### 3. Discord Bot & Ticket System Synchronization Linking your Discord account directly pairs with the **X02 Discord Ticket System** (`/discord-tickets`). When linked: * The web ticket dashboard automatically detects which Discord servers you manage. * Ticket creation buttons, staff response forms, and HTML transcripts are automatically attributed to your Discord user identity. --- ## Security Best Practices | Recommendation | Why It Matters | |---|---| | **Enable 2FA on Google & Discord** | Because social login gives instant access to your X02 dashboard, protecting your linked accounts with Two-Factor Authentication secures your file library. | | **Keep a Backup Password** | Setting a strong local password ensures you can always sign in even if an external provider experiences temporary downtime. | | **Review Connected Apps Periodically** | Periodically check your Account Connections in settings to remove unused or old third-party logins. | --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **Get your personal API key for scripts & tools** | [How to Get an API Key ](/blog/how-to-get-an-api-key) | | **Integrate ShareX for instant screenshot uploads** | [How to Integrate ShareX with X02 ](/blog/how-to-integrate-sharex-with-x02) | | **Switch between Short URLs and Subdomains** | [How to Switch Between Short URLs and Subdomains ](/blog/how-to-switch-between-short-urls-and-subdomains) | | **Organize your media with custom folders** | [How to Organize Files with Folders ](/blog/how-to-organize-files-with-folders) | --- # How to Get an API Key Source: https://x02.me/blog/how-to-get-an-api-key 2026-09-01 · X02 Team An **API Key** is a unique secret token that identifies your X02 account when uploading files outside of your web browser. You need an API key if you want to: * Connect desktop screenshot utilities like **ShareX** or **Flameshot**. * Automate uploads from a shell script, cron job, or CI pipeline with `curl`. * Write automated scripts in Python, Node.js, Go, or Bash. * Integrate build artifact uploads into GitHub Actions or GitLab CI/CD. This guide covers how to get your API key from the dashboard, how to generate one programmatically, and how to use it securely. > [!TIP] > **Quick Navigation:** > * [Getting Your API Key from the Dashboard](#part-1-the-easy-guide-for-everyone) > * [How to Regenerate a Compromised Key](#how-to-regenerate-your-api-key) > * [Authenticating API Requests (cURL, JS, Python)](#part-2-the-developer--power-user-guide) > * [Programmatic Key Generation Endpoint](#2-generating-a-key-programmatically-via-api) > * [Folder-Specific API Keys](#3-generating-folder-specific-api-keys) > * [Security Best Practices](#security-best-practices) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## Part 1: The Easy Guide (For Everyone) Every registered X02 user automatically gets a personal API key. You can find and copy it in less than 10 seconds. ![Architecture Diagram](/blog-diagrams/how-to-get-an-api-key-1.svg) ### Step-by-Step: Copying Your API Key | Where to Go | Direct Link | Action | | :--- | :--- | :--- | | **`Dashboard`** **`Settings`** **`API Keys`** | [Open Dashboard Settings](https://x02.me/dashboard) | Click **Copy API Key** | 1. **Log in** to your account at **[x02.me](https://x02.me)**. 2. Open your **Dashboard** and navigate to **Settings**. 3. Scroll to the **API Keys** section. 4. Click the **Copy** button next to your key. Your API key is now copied to your clipboard and ready to paste into ShareX or your custom scripts. --- ### How to Regenerate Your API Key If you accidentally exposed your API key on a public GitHub repository or livestream, you should regenerate it immediately: 1. Go to **Dashboard** **Settings** **API Keys**. 2. Click **Regenerate API Key**. 3. Confirm the prompt. > [!IMPORTANT] > Regenerating your API key immediately invalidates the old key. Be sure to update your ShareX profile, `.env` files, and CI/CD secrets with your new token. --- ## Part 2: The Developer & Power User Guide --- ### 1. Using Your API Key in HTTP Requests To authenticate requests to the X02 API, provide your key in the `x-api-key` header (or as a `Bearer` token in the `Authorization` header): #### cURL Example: ```bash curl -X POST "https://x02.me/api/upload" \ -H "x-api-key: YOUR_X02_API_KEY" \ -F "file=@screenshot.png" ``` #### Node.js / TypeScript Example: ```typescript const response = await fetch("https://x02.me/api/upload", { method: "POST", headers: { "x-api-key": process.env.X02_API_KEY!, "Accept": "application/json", }, body: formData, }); ``` #### Python Example: ```python import os import requests headers = { "x-api-key": os.getenv("X02_API_KEY"), "Accept": "application/json" } with open("report.pdf", "rb") as f: response = requests.post("https://x02.me/api/upload", headers=headers, files={"file": f}) print(response.json()) ``` --- ### 2. Generating a Key Programmatically via API If you are building an automated provisioning tool, you can request an API key programmatically: ```bash curl -X POST "https://x02.me/api/keys/generate" ``` #### JSON Response: ```json { "success": true, "data": { "apiKey": "x02_k928fa8bc910283...", "limits": { "dailyLimit": 50, "hourlyLimit": 20 } } } ``` --- ### 3. Generating Folder-Specific API Keys If you want an API key that automatically routes all uploaded files into a specific folder: ```bash curl -X POST "https://x02.me/api/user/folders/FOLDER_ID/key" \ -H "x-api-key: YOUR_PRIMARY_API_KEY" ``` Any upload sent with this key will land straight inside the designated folder without requiring extra folder parameters. --- ## Security Best Practices | Rule | Why It Matters | |---|---| | **Never commit keys to Git** | Use `.env` files and add `.env` to your `.gitignore` to prevent secret leaks. | | **Use GitHub Secrets in CI/CD** | Add your key under **Repository Settings** **Secrets and variables** **Actions** as `X02_API_KEY`. | | **Regenerate Leaked Keys Immediately** | If a key is exposed publicly, click Regenerate in your dashboard to revoke access instantly. | --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **Use your API key to connect ShareX** | [How to Integrate ShareX with X02 ](/blog/how-to-integrate-sharex-with-x02) | | **Upload files using cURL, the REST API, or MCP** | [How to Upload Files to X02 ](/blog/how-to-upload-files-to-x02) | | **Create dedicated upload keys for folders** | [How to Organize Files with Folders ](/blog/how-to-organize-files-with-folders) | | **Swap uploaded files via API** | [How to Replace a File Without Changing Its Link ](/blog/how-to-replace-file-without-changing-link) | --- # How to Integrate ShareX with X02 Source: https://x02.me/blog/how-to-integrate-sharex-with-x02 2026-09-01 · X02 Team If you regularly share screenshots, design mockups, error messages, or short screen clips on Discord, Slack, or GitHub, doing it manually is a major time sink: 1. Capture screen with snipping tool. 2. Save file to desktop. 3. Open browser and navigate to an image host. 4. Drag and drop file. 5. Wait for upload and copy link. That 30-second multi-step chore can be reduced to **a single 1-second keystroke**. By integrating **ShareX** with **X02**, pressing a shortcut key instantly captures your screen, uploads the image in full quality, and places the direct link directly into your clipboard ready to paste. > [!TIP] > **Quick Navigation:** > * [60-Second One-Click Setup](#part-1-the-quick--easy-guide-under-60-seconds) > * [Recommended Hotkey List](#step-5-recommended-hotkeys) > * [Direct-to-Folder Uploads](#2-direct-to-folder-uploads-work-vs-personal-profiles) > * [Ephemeral Self-Destructing Screenshots](#3-auto-expiring-ephemeral-screenshots) > * [Manual SXCU Configuration (For Devs)](#1-manual-sxcu-file-structure) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## What is ShareX? (And Why Pair It with X02?) **[ShareX](https://getsharex.com)** is the gold standard of open-source screen capture and productivity tools for Windows. It is 100% free, lightweight, ad-free, and loaded with built-in annotation tools, screen recorders, color pickers, and OCR text extractors. ### How ShareX + X02 Compares to Alternatives | Tool | Drawbacks | Why ShareX + X02 Wins | |---|---|---| | **Lightshot** | Slow ad-filled landing pages, public predictable URLs | **Instant direct URLs**, private unguessable links, custom subdomains (`you.x02.me`) | | **Gyazo** | 10-image history limit on free tier, watermarks, slow embeds | **300 MB uploads**, permanent history, zero watermarks, folder organization | | **Imgur** | Heavy compression, deletes inactive links, restrictive terms | **Lossless quality**, permanent hosting, instant in-place replacement | | **Windows Snipping Tool** | No cloud upload; requires manual saving and uploading | **Automated background cloud upload** in under 500ms | > [!NOTE] > **Using macOS or Linux?** While ShareX is Windows-only, you can achieve the exact same 1-keystroke workflow on Linux using **Flameshot**, or on macOS with a short shell script that pipes the capture straight to the upload endpoint (detailed in Part 2). --- ![Architecture Diagram](/blog-diagrams/how-to-integrate-sharex-with-x02-1.svg) --- ## Part 1: The Quick & Easy Guide (Under 60 Seconds) You don't need to manually configure complicated API endpoints. X02 provides a pre-configured, one-click integration file (`.sxcu`). ### Step 1: Install ShareX If you haven't already, download and install ShareX from **[getsharex.com](https://getsharex.com)** (or install it via Microsoft Store / Steam). --- ### Step 2: Download Your X02 Integration Profile | Where to Go | Direct Link | Action | | :--- | :--- | :--- | | **`Dashboard`** **`Settings`** **`Integrations`** **`ShareX`** | [Open Dashboard Settings](https://x02.me/dashboard) | Click **Download .sxcu File** | --- ### Step 3: One-Click Import 1. Double-click the downloaded `.sxcu` file on your computer. 2. ShareX will open a prompt asking: *"Would you like to set X02 as your active custom uploader?"* 3. Click **Yes**. That's it! X02 is now your default image uploader. --- ### Step 4: Configure "After Capture Tasks" To ensure your screenshots upload immediately and copy the link to your clipboard: 1. Open the main ShareX window. 2. In the left sidebar, click **After capture tasks**. 3. Ensure the following two items have a checkmark: * **Upload image to host** * **Copy URL to clipboard** --- ### Step 5: Recommended Hotkeys Set up your preferred shortcuts in ShareX under **Hotkey settings**: | Action | Recommended Key | What It Does | |---|---|---| | **Capture Region** | `Ctrl + Shift + S` or `Print Screen` | Lets you draw a box around any part of your screen with annotation tools. | | **Capture Entire Screen** | `Ctrl + Print Screen` | Instantly captures all monitors and uploads. | | **Capture Active Window** | `Alt + Print Screen` | Captures only the currently focused application window. | | **Screen Recording (GIF / MP4)** | `Shift + Print Screen` | Records a short animated clip and uploads it directly. | --- ## Part 2: The Developer & Power User Guide For users who want custom configurations, direct-to-folder routing, auto-expiring links, or manual SXCU editing, here is the technical breakdown. --- ### 1. Manual SXCU File Structure ShareX uses JSON `.sxcu` (ShareX Custom Uploader) configuration files. If you want to create or edit your configuration file manually: ```json { "Version": "15.0.0", "Name": "X02 Cloud Uploader", "DestinationType": "ImageUploader, TextUploader, FileUploader", "RequestMethod": "POST", "RequestURL": "https://up.x02.me/api/upload", "Headers": { "x-api-key": "YOUR_X02_API_KEY_HERE" }, "Body": "MultipartFormData", "FileFormName": "file", "URL": "{json:url}", "ThumbnailURL": "{json:thumbnailUrl}", "DeletionURL": "{json:deleteUrl}", "ErrorMessage": "{json:error}" } ``` #### Key Fields Explained: * **`RequestURL`:** `https://up.x02.me/api/upload` (X02's high-speed edge upload gateway). * **`Headers.x-api-key`:** Your private API key from your dashboard. * **`FileFormName`:** Set to `"file"` (or `"image"`). * **`URL: "{json:url}"`:** Instructs ShareX to parse the JSON response and copy the direct link (`https://x02.me/i/xxxx.png`) to your clipboard. --- ### 2. Direct-to-Folder Uploads (Work vs. Personal Profiles) Want work screenshots to land in a `Work` folder, and gaming clips in a `Gaming` folder? You can create multiple ShareX hotkeys tied to specific folders using **Folder Upload Keys**. | Path | Direct Action | | :--- | :--- | | **`Dashboard`** **`Folders`** `[Select Folder]` | Click **Generate Folder Key** | 1. In your **X02 Dashboard**, open your target folder and click **Generate Folder Key** (or use the API endpoint `POST /api/user/folders/:id/key`). 2. In ShareX, open **Destinations** → **Custom uploader settings**. 3. Duplicate your X02 profile, name it `X02 - Work`, and replace the `x-api-key` header with your **Folder Key**. 4. In ShareX **Hotkey settings**, create a new hotkey (e.g. `Ctrl + Shift + W`) and set its destination to `X02 - Work`. Every screenshot taken with that hotkey will now automatically sort into your Work folder! --- ### 3. Auto-Expiring Ephemeral Screenshots If you frequently share temporary debug logs, bug snippets, or sensitive credentials that shouldn't stay hosted forever, add the `expiry` parameter to your SXCU: ```json { "RequestURL": "https://up.x02.me/api/upload?expiry=1d", "Headers": { "x-api-key": "YOUR_X02_API_KEY" } } ``` * Supported expiry values: `1h`, `6h`, `1d`, `7d`, `30d`. * Files automatically self-destruct once the timer expires. --- ### 4. Linux & macOS Integration (Flameshot Alternative) If you are on Linux or macOS, you can achieve the same workflow with **Flameshot** and a one-line `curl` upload. Flameshot writes the capture to stdout with `--raw`, and X02 returns the hosted URL as plain text, so the whole thing pipes straight into your clipboard: ```bash # Linux (xclip) flameshot gui --raw \ | curl -s -H "x-api-key: YOUR_API_KEY" -F "file=@-;filename=capture.png" \ https://up.x02.me/api/upload \ | xclip -selection clipboard # macOS (pbcopy) flameshot gui --raw \ | curl -s -H "x-api-key: YOUR_API_KEY" -F "file=@-;filename=capture.png" \ https://up.x02.me/api/upload \ | pbcopy ``` Save that as a shell script and bind it to a hotkey to get the same one-keystroke capture-and-share loop as ShareX. --- ## Troubleshooting Checklist * **Error: "Upload failed: API key required"** Ensure your API key is correctly pasted into the `Headers` section under `x-api-key` without extra spaces. * **Screenshot captures but link isn't copied to clipboard** In ShareX, verify that **After capture tasks** → **Copy URL to clipboard** is checked. * **ShareX uploads to Imgur instead of X02** Go to **Destinations** → **Image uploader** and select **Custom image uploader**. --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **Organize screenshots into custom folders** | [How to Organize Files with Folders ](/blog/how-to-organize-files-with-folders) | | **Swap or update a screenshot without changing the link** | [How to Replace a File Without Changing Its Link ](/blog/how-to-replace-file-without-changing-link) | | **Give your screenshots custom URLs** | [How to Rename Your Uploaded Files ](/blog/how-to-rename-uploaded-files) | | **Explore all other upload channels** | [How to Upload Files to X02 ](/blog/how-to-upload-files-to-x02) | --- # How to Organize Files with Folders Source: https://x02.me/blog/how-to-organize-files-with-folders 2026-09-01 · X02 Team When you start uploading dozens or hundreds of screenshots, mockups, design assets, and documents, finding what you need in a single flat gallery can quickly become chaotic. **X02 provides a folder system** designed to keep your media organized, searchable, and easy to share. Whether you want to categorize personal screenshots, route API uploads into specific project directories, or share password-protected albums with clients, folders make managing your uploads effortless. > [!TIP] > **Quick Navigation:** > * [Creating Folders & Moving Files](#part-1-the-easy-guide-for-everyone) > * [Folder Privacy & Password Protection](#4-folder-privacy--public-sharing) > * [Team Collaboration & Roles](#5-collaborating-with-team-members) > * [Direct-to-Folder API Keys](#1-dedicated-folder-api-keys-direct-to-folder-uploads) > * [Developer API & Batch Operations](#2-creating-a-folder-via-rest-api) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## Part 1: The Easy Guide (For Everyone) Organizing your gallery is visual, intuitive, and takes only a few seconds from your dashboard. ![Architecture Diagram](/blog-diagrams/how-to-organize-files-with-folders-1.svg) ### 1. Creating a Folder | Where to Go | Direct Link | Action | | :--- | :--- | :--- | | **`Dashboard`** **`Gallery Toolbar`** **`New Folder (+)`** | [Open Dashboard](https://x02.me/dashboard) | Name folder & choose accent color | 1. Log in to your account at **[x02.me](https://x02.me)** and open your **Dashboard**. 2. In the gallery toolbar, click **New Folder** (or the `+` icon). 3. Enter a folder name (e.g. `Client Previews`, `Website Assets`, or `Receipts`). 4. Pick a **custom color** and **icon** to make your folder easy to identify at a glance. 5. Click **Create Folder**. > [!NOTE] > Free accounts can create up to **10 custom folders**, while Pro users enjoy **unlimited folders**. --- ### 2. Moving Files into Folders X02 makes organizing files fast and flexible, whether you're moving a single file or hundreds at once: #### Moving an Individual File 1. Find the file card in your **Dashboard Gallery**. 2. Click the **Folder icon** on the card actions bar (or click the image to open the full preview and select **Move to Folder**). 3. A modal will list all your folders. Click on your target folder (e.g. `Design Assets`). 4. The file moves instantly. #### Batch Moving Multiple Files 1. Check the top-left checkboxes on the cards you want to organize (or click **Select All** in the toolbar). 2. Click the **Move Selected** button in the floating bulk action bar. 3. Select the destination folder from the dropdown. 4. All selected files are moved simultaneously in a single operation. #### Moving Files Back to Root (Removing from Folder) If you want to take a file out of a folder and return it to your main unorganized gallery: 1. Open the folder where the file currently resides. 2. Select the file(s) and click **Move to Folder**. 3. Select **All Files (Root)**. 4. The files will now appear in your general library without a folder tag. --- ### 3. Setting a Default Upload Folder If you're currently working on a specific project, you can set a folder as your **Default Upload Destination**. Every new file you upload through the web browser, clipboard paste (`Ctrl+V`), or API will automatically land inside that folder without needing manual sorting. --- ### 4. Folder Privacy & Public Sharing You decide who has access to your folder contents: | Privacy Mode | Access Level | Best For | |---|---|---| | **Private (Default)** | Only visible to you and invited collaborators | Work drafts, personal receipts, private backups | | **Public Share Link** | Accessible to anyone with the unique link (`https://x02.me/f/share-token`) | Public image galleries, community wallpaper packs | | **Password Protected** | Accessible only after entering a password | Embargoed press kits, confidential client previews | --- ### 5. Collaborating with Team Members You can invite other X02 users into your folders with granular permissions: * **Admin:** Full access to manage folder settings, privacy, add/remove collaborators, upload, rename, and delete files. * **Editor:** Can upload new files and rename existing files within the folder. * **Viewer:** Read-only access to view and download files. To invite a teammate, open the folder in your dashboard, click **Collaborators**, enter their username or email, and assign their role. --- ## Part 2: The Developer & Power User Guide For engineers and automation workflows, X02 exposes REST endpoints for folder creation, batch file migration, and dedicated folder upload keys. --- ### 1. Dedicated Folder API Keys (Direct-to-Folder Uploads) Instead of uploading a file and calling a move endpoint afterward, you can generate a **Folder-Specific Upload Key**. Any upload authenticated with this key is automatically routed directly into that folder upon ingestion. #### Step 1: Generate the Folder Key ```bash curl -X POST "https://x02.me/api/user/folders/FOLDER_ID/key" \ -H "x-api-key: YOUR_PRIMARY_API_KEY" ``` **Response:** ```json { "success": true, "apiKey": "x02_fld_key_82910fa8bc91", "folderId": "FOLDER_ID", "folderName": "Build Artifacts" } ``` #### Step 2: Upload Directly to the Folder ```bash curl -X POST "https://x02.me/api/upload" \ -H "x-api-key: x02_fld_key_82910fa8bc91" \ -F "file=@test-report.pdf" ``` --- ### 2. Creating a Folder via REST API Send a `POST` request to `/api/user/folders`: ```bash curl -X POST "https://x02.me/api/user/folders" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "CI Release Drops", "description": "Automated build logs from GitHub Actions", "color": "#3b82f6", "icon": "folder-git" }' ``` #### Successful JSON Response: ```json { "success": true, "folder": { "id": "fld_9a82b1c", "name": "CI Release Drops", "slug": "ci-release-drops", "fileCount": 0, "createdAt": "2026-09-01T16:00:00Z" } } ``` --- ### 3. Moving Files via API (cURL, TypeScript, Python) Move one or more files in a single atomic request using `PATCH /api/user/images/move`: #### cURL Example: ```bash curl -X PATCH "https://x02.me/api/user/images/move" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filenames": ["coverage_report.png", "benchmark.pdf"], "folderId": "fld_9a82b1c" }' ``` #### Node.js / TypeScript Example: ```typescript async function batchMoveFiles(filenames: string[], targetFolderId: string | null, apiKey: string) { const response = await fetch("https://x02.me/api/user/images/move", { method: "PATCH", headers: { "x-api-key": apiKey, "Content-Type": "application/json", "Accept": "application/json", }, body: JSON.stringify({ filenames, folderId: targetFolderId, // Pass null to move back to Root }), }); if (!response.ok) { const err = await response.json(); throw new Error(`Failed to move files: ${err.error}`); } const result = await response.json(); console.log(`Successfully moved ${filenames.length} files to folder ${targetFolderId}`); return result; } // Example usage: Move files to folder batchMoveFiles(["coverage_report.png", "benchmark.pdf"], "fld_9a82b1c", "YOUR_API_KEY") .catch(console.error); ``` #### Python Example: ```python import requests def move_files_to_folder(filenames: list, folder_id: str or None, api_key: str): url = "https://x02.me/api/user/images/move" headers = { "x-api-key": api_key, "Content-Type": "application/json", "Accept": "application/json" } payload = { "filenames": filenames, "folderId": folder_id # Pass None to move back to Root } response = requests.patch(url, headers=headers, json=payload) if response.status_code == 200: print("Files moved successfully:", response.json()) return response.json() else: print(f"Error ({response.status_code}):", response.text) response.raise_for_status() # Example usage move_files_to_folder(["coverage_report.png"], "fld_9a82b1c", "YOUR_API_KEY") ``` --- ### 4. Updating Folder Privacy & Password Protection You can toggle public sharing or set password protection on any folder via API: ```bash curl -X PATCH "https://x02.me/api/user/folders/FOLDER_ID/privacy" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "isShareable": true, "isPasswordProtected": true, "password": "ClientSecurePass2026!" }' ``` --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **Send ShareX screenshots directly into folders** | [How to Integrate ShareX with X02 ](/blog/how-to-integrate-sharex-with-x02) | | **Rename assets for clean URL paths** | [How to Rename Your Uploaded Files ](/blog/how-to-rename-uploaded-files) | | **Update or replace files without breaking links** | [How to Replace a File Without Changing Its Link ](/blog/how-to-replace-file-without-changing-link) | | **Master all upload methods** | [How to Upload Files to X02 ](/blog/how-to-upload-files-to-x02) | --- # How to Rename Your Uploaded Files Source: https://x02.me/blog/how-to-rename-uploaded-files 2026-09-01 · X02 Team When you capture a quick screenshot or export a file from an app, it often gets a messy, auto-generated name like `Screenshot_2026-09-01_16.03.22.png` or `IMG_9042_final_v2.jpg`. Sharing links with chaotic filenames looks unprofessional, makes Discord embeds look untidy, and makes it almost impossible to search for files later in your library. On **X02**, you can easily rename any file in your gallery to create clean, human-readable links (like `https://x02.me/i/dashboard-preview.png` or `https://alex.x02.me/i/resume-2026.pdf`). > [!TIP] > **Quick Navigation:** > * [Renaming Files from Dashboard](#step-by-step-renaming-a-file-from-the-dashboard) > * [Helpful Naming Rules](#helpful-naming-tips) > * [Storage Migration & Sanitization](#1-how-renaming-works-under-the-hood) > * [Developer API Endpoints](#2-renaming-a-file-via-curl-terminal) > * [Role Permissions (Collaborators)](#role-based-permissions-collaborative-workspaces) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## Part 1: The Easy Guide (For Everyone) Renaming your files takes just a couple of clicks from your personal dashboard. ![Architecture Diagram](/blog-diagrams/how-to-rename-uploaded-files-1.svg) ### Step-by-Step: Renaming a File from the Dashboard | Where to Go | Direct Link | Action | | :--- | :--- | :--- | | **`Dashboard`** **`Gallery`** `[Hover File Card]` | [Open Dashboard Gallery](https://x02.me/dashboard) | Click **Rename** button | 1. **Log in** to your account on **[x02.me](https://x02.me)** and open your **Dashboard**. 2. Find the file in your **Gallery**. 3. Hover over the file card and click the **Rename** button (or open the **Media Preview Modal** and click **Rename** from the actions menu). 4. Enter your new filename (e.g. `q3-financial-chart.png`). 5. Click **Save / Rename**. Your file is now updated, and you can copy your clean new link immediately. --- ### Helpful Naming Tips * **Always Include the File Extension:** Make sure to keep the `.png`, `.jpg`, `.mp4`, or `.pdf` extension at the end so apps know how to display the file. * **Use Hyphens Instead of Spaces:** Spaces are automatically turned into hyphens (e.g. `my cool screenshot.png` becomes `my-cool-screenshot.png`) so your link works cleanly on all web browsers and Discord. * **Keep It Descriptive:** Use names that make finding the file effortless when using the gallery search bar. --- ## Part 2: The Developer & Power User Guide For developers managing large asset libraries, bots, or CI/CD pipelines, X02 provides a dedicated REST API endpoint for programmatic file renaming. --- ### 1. How Renaming Works Under the Hood When a rename request is sent: 1. **Sanitization & Validation:** The backend trims whitespace, replaces spaces with hyphens, sanitizes illegal path traversal characters, and validates extension formats. 2. **Atomic Storage Migration:** On cloud storage (R2/S3), an atomic `CopyObject` operation copies the file to the new key destination, followed by a `DeleteObject` on the old key to prevent data loss. 3. **MIME Type Updating:** If the extension is changed (e.g. from `.jpeg` to `.jpg`), metadata directives automatically update the `Content-Type` headers. 4. **Cache Invalidation:** The old URL is purged from edge caches so it stops serving stale data, while the new URL immediately becomes active globally. 5. **Collision Protection:** If another file with the exact same name already exists in your account, X02 returns a `409 Conflict` error to prevent accidental overwrites. --- ### 2. Renaming a File via cURL (Terminal) Send a `PUT` request to `/api/user/images/:filename/rename` with your API key: ```bash curl -X PUT "https://x02.me/api/user/images/old-name.png/rename" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "newFilename": "clean-release-diagram.png" }' ``` #### Successful JSON Response: ```json { "success": true, "message": "File renamed successfully", "oldFilename": "old-name.png", "filename": "clean-release-diagram.png", "url": "https://x02.me/i/clean-release-diagram.png" } ``` --- ### 3. Renaming in JavaScript / TypeScript (Node.js & Browser) ```typescript async function renameFile(oldName: string, newName: string, apiKey: string) { const response = await fetch(`https://x02.me/api/user/images/${oldName}/rename`, { method: "PUT", headers: { "x-api-key": apiKey, "Content-Type": "application/json", "Accept": "application/json", }, body: JSON.stringify({ newFilename: newName }), }); if (!response.ok) { const errorData = await response.json(); throw new Error(`Rename failed: ${errorData.error || response.statusText}`); } const result = await response.json(); console.log("Renamed to:", result.url); return result; } // Example usage renameFile("IMG_1024.png", "hero-banner.png", "YOUR_API_KEY") .catch(console.error); ``` --- ### 4. Renaming in Python ```python import requests def rename_file(old_filename: str, new_filename: str, api_key: str): url = f"https://x02.me/api/user/images/{old_filename}/rename" headers = { "x-api-key": api_key, "Content-Type": "application/json", "Accept": "application/json" } payload = {"newFilename": new_filename} response = requests.put(url, headers=headers, json=payload) if response.status_code == 200: data = response.json() print(f"Renamed successfully: {data['url']}") return data else: print(f"Error ({response.status_code}):", response.text) response.raise_for_status() # Example usage rename_file("screencap_9182.png", "system-architecture.png", "YOUR_API_KEY") ``` --- ## Role-Based Permissions (Collaborative Workspaces) If you share media folders with team members on X02: | Role | Can Rename Files? | Description | |---|---|---| | **Owner** | Yes | Full control over all assets | | **Admin** | Yes | Can rename, replace, and organize folder contents | | **Editor** | Yes | Can upload and rename files within the shared folder | | **Viewer** | No | Read-only access to view and download files | --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **Organize renamed files into folders** | [How to Organize Files with Folders ](/blog/how-to-organize-files-with-folders) | | **Swap file content without changing the link** | [How to Replace a File Without Changing Its Link ](/blog/how-to-replace-file-without-changing-link) | | **Connect ShareX for instant captures** | [How to Integrate ShareX with X02 ](/blog/how-to-integrate-sharex-with-x02) | | **Explore all upload channels** | [How to Upload Files to X02 ](/blog/how-to-upload-files-to-x02) | --- # How to Replace a File Without Changing Its Link Source: https://x02.me/blog/how-to-replace-file-without-changing-link 2026-09-01 · X02 Team We've all been there: you upload a screenshot, publish a guide, send out a PDF report, or post an image link to Discord and Twitter—only to notice a glaring typo, an outdated price tag, or an old UI mockup five minutes later. On traditional image hosts, fixing this is painful. You have to delete the file, re-upload the new version, get a completely new URL, and then manually edit every tweet, forum post, GitHub readme, or document where the old link was shared. If you don't control the place where the link was posted, you're stuck with a broken link. **X02 solves this with In-Place File Replacement.** You can swap the underlying file anytime while keeping the exact same public URL, folder placement, and privacy settings. > [!TIP] > **Quick Navigation:** > * [2-Click Dashboard Replacement](#step-by-step-replacing-a-file-from-the-dashboard) > * [Common Everyday Use Cases](#common-everyday-use-cases) > * [How It Operates Under the Hood](#1-how-replacement-operates-under-the-hood) > * [Developer API & Code Examples](#2-replacing-a-file-via-curl-terminal) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## Part 1: The Easy Guide (For Everyone) You don't need any technical skills to replace a file. You can do it in two clicks directly from your personal dashboard. ![Architecture Diagram](/blog-diagrams/how-to-replace-file-without-changing-link-1.svg) ### What Happens When You Replace a File? * **Your link stays 100% identical:** Any existing links (`https://x02.me/i/yourfile.png`) or custom subdomain links (`https://yourname.x02.me/i/yourfile.png`) immediately display your new content. * **Metadata stays intact:** The file's creation date is preserved. * **Folder & privacy settings don't change:** If the file was in a specific folder or password-protected album, it stays right where you organized it. * **Old content is permanently erased:** The previous version is completely overwritten in storage. --- ### Step-by-Step: Replacing a File from the Dashboard | Where to Go | Direct Link | Action | | :--- | :--- | :--- | | **`Dashboard`** **`Gallery`** `[Hover File Card]` | [Open Dashboard Gallery](https://x02.me/dashboard) | Click **Replace** (Swap Icon) | 1. **Log in** to your account at **[x02.me](https://x02.me)** and go to your **Dashboard**. 2. Find the file you want to update in your **Gallery**. 3. Hover over the file card and click the **Replace** button (or click the file to open the full preview modal and select **Replace** from the menu). 4. Select your new file from your computer. 5. Confirm the swap. That's it! In less than two seconds, anyone opening your original link will see the new version. > [!TIP] > **Power-User Shortcut:** If you replace files frequently, you can turn on **"Bypass Confirmation"** in your gallery settings to replace files immediately with a single click. --- ### Common Everyday Use Cases * **Discord & Social Media Announcements:** Fix mistakes in posted event flyers or schedules without deleting and re-sending messages. * **Documentation & Readmes:** Update software screenshots in GitHub readmes without having to commit new image links to Git history. * **Resumes & Client Documents:** Update your portfolio or PDF resume while keeping the link you sent to hiring managers active. * **Live Product Banners:** Swap promotional graphics on websites or forums without editing HTML embed tags. --- ## Part 2: The Developer & Power User Guide For developers building automated pipelines, release scripts, or dynamic assets, X02 exposes an in-place replacement endpoint over REST API. --- ### 1. How Replacement Operates Under the Hood When you perform an in-place replacement: 1. **Object Key Persistence:** Rather than generating a new random UUID or slug, the backend retrieves the file's existing storage key (`r2Key` or disk path) and overwrites the binary buffer directly. 2. **Automated Safety Re-Verification:** The newly uploaded binary is asynchronously checked by X02's automated moderation pipeline to ensure it meets platform guidelines. 3. **Database Metadata Delta:** The database recalculates the file size, hash (`SHA-256`), and MIME type, updating your account's storage usage delta while preserving the original upload timestamp. 4. **Cache-Control & Instant Edge Purging:** The asset is served with cache-revalidation headers (`public, max-age=0, s-maxage=31536000, must-revalidate`), prompting intermediate proxies, Cloudflare edge nodes, and browsers to revalidate and serve the new asset immediately. --- ### 2. Replacing a File via cURL (Terminal) Send a `PATCH` request to `/api/user/images/:filename/replace` with your API key: ```bash curl -X PATCH "https://x02.me/api/user/images/ABCD123.png/replace" \ -H "x-api-key: YOUR_X02_API_KEY" \ -F "file=@/path/to/new-graphic.png" ``` #### Successful JSON Response: ```json { "success": true, "message": "File replaced successfully", "filename": "ABCD123.png", "size": 184520, "mimeType": "image/png", "url": "https://x02.me/i/ABCD123.png" } ``` --- ### 3. Replacing a File in JavaScript / TypeScript (Node.js) Automate asset replacements in build scripts or continuous integration workflows: ```typescript import fs from "fs"; async function replaceHostedFile(filename: string, newFilePath: string, apiKey: string) { const formData = new FormData(); const fileBuffer = fs.readFileSync(newFilePath); formData.append("file", new Blob([fileBuffer]), filename); const response = await fetch(`https://x02.me/api/user/images/${filename}/replace`, { method: "PATCH", headers: { "x-api-key": apiKey, "Accept": "application/json", }, body: formData, }); if (!response.ok) { const error = await response.json(); throw new Error(`Failed to replace file: ${error.error || response.statusText}`); } const result = await response.json(); console.log("File successfully replaced at:", result.url); return result; } // Example usage replaceHostedFile("banner_q3.png", "./assets/banner_q3_final.png", "YOUR_API_KEY") .catch(console.error); ``` --- ### 4. Replacing a File in Python A clean Python automation script using `requests`: ```python import requests def replace_file(filename: str, new_file_path: str, api_key: str): url = f"https://x02.me/api/user/images/{filename}/replace" headers = { "x-api-key": api_key, "Accept": "application/json" } with open(new_file_path, "rb") as f: files = {"file": f} response = requests.patch(url, headers=headers, files=files) if response.status_code == 200: data = response.json() print(f"Success! File replaced at: {data['url']}") return data else: print(f"Error ({response.status_code}):", response.text) response.raise_for_status() # Example usage replace_file("test-build.zip", "./dist/test-build-v2.zip", "YOUR_API_KEY") ``` --- ### 5. Replacing Files from a Shell Script The same endpoint works from a one-liner, which is handy in a deploy step or a cron job that keeps a hosted asset current: ```bash curl -X PATCH "https://up.x02.me/api/user/images/ABCD123.png/replace" -H "x-api-key: YOUR_API_KEY" -F "file=@./updated_screenshot.png" ``` --- ## Best Practices & Tips | Tip | Why It Matters | |---|---| | **Match Format When Possible** | While you can swap a `.png` for another `.png`, keeping the file extension consistent prevents client-side rendering issues with static image tags. | | **Check Storage Limits** | If the replacement file is significantly larger than the old one, ensure your account has sufficient quota to cover the size difference. | | **Allow a Few Seconds for Global CDN Propagation** | While edge cache headers revalidate immediately, some aggressive browser caches may need a hard refresh (`Ctrl + F5` or `Cmd + Shift + R`). | --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **Rename files to clean URLs** | [How to Rename Your Uploaded Files ](/blog/how-to-rename-uploaded-files) | | **Organize uploads into custom folders** | [How to Organize Files with Folders ](/blog/how-to-organize-files-with-folders) | | **Connect ShareX for 1-second captures** | [How to Integrate ShareX with X02 ](/blog/how-to-integrate-sharex-with-x02) | | **Explore all upload channels** | [How to Upload Files to X02 ](/blog/how-to-upload-files-to-x02) | --- # How to Set a Default Upload Folder Source: https://x02.me/blog/how-to-set-a-default-upload-folder 2026-09-01 · X02 Team If you are working on a specific design project, organizing screenshots for a client review, or recording clips for an upcoming video, manually moving every new upload from your main gallery into a folder takes unnecessary time. **X02 allows you to set a Default Upload Folder.** Once set, every file you upload—whether via web drag-and-drop, clipboard paste (`Ctrl+V`), ShareX, an AI assistant over MCP, or the standard API—automatically lands straight inside your chosen folder. > [!TIP] > **Quick Navigation:** > * [Why Use a Default Folder](#why-use-a-default-upload-folder) > * [1-Click Dashboard Setup](#part-1-the-easy-guide-for-everyone) > * [Resetting Back to Root Gallery](#resetting-back-to-root-all-files) > * [Developer API Endpoint](#part-2-the-developer--power-user-guide) > * [Default Folders vs. Folder Upload Keys](#default-folder-vs-folder-upload-keys) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## Why Use a Default Upload Folder? * **Zero Manual Sorting:** Eliminate the need to select files and click "Move to Folder" after every upload. * **Project-Based Workflows:** Set a dedicated folder (e.g. `Client Redesign 2026`) as your default for the week, then switch it when your project finishes. * **Cleaner Root Library:** Keep your main unorganized gallery free from clutter. * **Works Across All Ingestion Channels:** Automatically applies to web uploads, clipboard snips, desktop tools, and developer scripts. --- ![Architecture Diagram](/blog-diagrams/how-to-set-a-default-upload-folder-1.svg) --- ## Part 1: The Easy Guide (For Everyone) You can set or switch your default upload folder in one click directly from your dashboard. ### Step-by-Step: Setting Your Default Folder | Where to Go | Direct Link | Action | | :--- | :--- | :--- | | **`Dashboard`** **`Gallery Toolbar`** `[Select Folder]` | [Open Dashboard Gallery](https://x02.me/dashboard) | Click **Set as Default Folder** | 1. **Log in** to your account at **[x02.me](https://x02.me)** and open your **Dashboard**. 2. In the gallery toolbar or folder sidebar, click on the folder you want to make your default (e.g. `Work Screenshots`). 3. Click the folder options menu and select **Set as Default Upload Folder**. 4. You will see a confirmation notification, and the folder will now display a **Default** indicator. From now on, any file you drag onto the homepage, paste via `Ctrl+V`, or capture with ShareX will be saved directly into that folder. --- ### Resetting Back to Root (All Files) When you finish your project and want uploads to land in your root gallery again: 1. In your dashboard, click **All Files (Root)** in the folder list. 2. Click **Set as Default** (or choose *Clear Default Folder*). 3. New uploads will now land in your general gallery as normal. --- ## Part 2: The Developer & Power User Guide For automated scripts, bots, and CI/CD pipelines, X02 allows you to programmatically read and update your default upload destination via REST API. --- ### 1. Setting Default Folder via API Send a `POST` request to `/api/user/default-folder` with the target `folderId`: ```bash curl -X POST "https://x02.me/api/user/default-folder" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "folderId": "fld_92k8a1b" }' ``` #### Successful JSON Response: ```json { "success": true, "message": "Default upload folder updated successfully", "defaultFolderId": "fld_92k8a1b" } ``` --- ### 2. Clearing the Default Folder (Reset to Root) To reset your account default back to root, pass `folderId: null`: ```bash curl -X POST "https://x02.me/api/user/default-folder" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "folderId": null }' ``` --- ### 3. JavaScript / TypeScript Implementation (Node.js & Browser) ```typescript async function setDefaultUploadFolder(folderId: string | null, apiKey: string) { const response = await fetch("https://x02.me/api/user/default-folder", { method: "POST", headers: { "x-api-key": apiKey, "Content-Type": "application/json", "Accept": "application/json", }, body: JSON.stringify({ folderId }), }); if (!response.ok) { const err = await response.json(); throw new Error(`Failed to set default folder: ${err.error}`); } const result = await response.json(); console.log("Default folder updated:", result); return result; } // Example usage: Set active project folder setDefaultUploadFolder("fld_92k8a1b", "YOUR_API_KEY") .catch(console.error); ``` --- ### 4. Python Implementation ```python import requests def set_default_folder(folder_id: str or None, api_key: str): url = "https://x02.me/api/user/default-folder" headers = { "x-api-key": api_key, "Content-Type": "application/json", "Accept": "application/json" } payload = {"folderId": folder_id} response = requests.post(url, headers=headers, json=payload) if response.status_code == 200: print("Default folder updated successfully:", response.json()) return response.json() else: print(f"Error ({response.status_code}):", response.text) response.raise_for_status() # Example usage set_default_folder("fld_92k8a1b", "YOUR_API_KEY") ``` --- ## Default Folder vs. Folder Upload Keys X02 offers two distinct ways to route uploads into folders: | Feature | How It Works | Best Use Case | |---|---|---| | **Default Upload Folder** | Changes your entire account's global default upload destination. | Temporary active projects, browser uploads, clipboard pastes. | | **Folder Upload Keys** | Generates a unique API key hardwired to a specific folder. | Multiple ShareX profiles (e.g. Work hotkey vs Gaming hotkey) or multi-repository CI/CD drops. | --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **Learn full folder organization and sharing** | [How to Organize Files with Folders ](/blog/how-to-organize-files-with-folders) | | **Get your API key for automated workflows** | [How to Get an API Key ](/blog/how-to-get-an-api-key) | | **Connect ShareX for instant desktop snips** | [How to Integrate ShareX with X02 ](/blog/how-to-integrate-sharex-with-x02) | | **Swap uploaded files without breaking links** | [How to Replace a File Without Changing Its Link ](/blog/how-to-replace-file-without-changing-link) | --- # How to Switch Between Short URLs and Subdomain URLs Source: https://x02.me/blog/how-to-switch-between-short-urls-and-subdomains 2026-09-01 · X02 Team When you upload a file to X02, you can choose how your public links look. Depending on where you share your content, you might want a compact, minimalist link for fast chat messages or a branded, personalized link that highlights your username. X02 gives every registered user the ability to switch between URL styles: 1. **Short URLs (`https://x02.me/u/...`):** Compact, neutral, and minimalist. 2. **Personal Subdomain URLs (`https://username.x02.me/i/...`):** Branded and customized to your account handle. 3. **Custom Domain URLs (`https://img.yourbrand.com/i/...`):** A domain you personally own — **Pro plan only**. See the [Custom Domain Setup Guide](/blog/branded-file-hosting-custom-subdomains) for DNS configuration. > [!TIP] > **Quick Navigation:** > * [Comparison: The Three Link Formats](#understanding-the-two-link-formats) > * [1-Click Dashboard Switch](#part-1-the-easy-guide-for-everyone) > * [Developer API Endpoints](#part-2-the-developer--power-user-guide) > * [Wildcard DNS & Edge Routing Mechanics](#3-how-subdomain-routing-works-under-the-hood) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## Understanding the Two Link Formats | Format | Example URL | Best Used For | Plan | |---|---|---|---| | **Short URL** | `https://x02.me/u/A8j2P.png` | Character-limited posts on X/Twitter, discreet file drops, compact links | Free | | **Personal Subdomain** | `https://alex.x02.me/i/A8j2P.png` | Personal portfolios, Discord communities, branded documentation, developer projects | Free | | **Custom Domain** | `https://img.alex.dev/i/A8j2P.png` | Company branding, client-facing deliverables, full domain ownership | Pro | --- ![Architecture Diagram](/blog-diagrams/how-to-switch-between-short-urls-and-subdomains-1.svg) --- ## Part 1: The Easy Guide (For Everyone) Switching your default link format takes only one click from your dashboard. ### Step-by-Step: Changing Your URL Format | Where to Go | Direct Link | Action | | :--- | :--- | :--- | | **`Dashboard`** **`Settings`** **`URL Format`** | [Open Dashboard Settings](https://x02.me/dashboard) | Select **Subdomain** or **Short URL** | 1. **Log in** to your account at **[x02.me](https://x02.me)** and open your **Dashboard**. 2. Click **Settings** in the navigation bar. 3. Find the **URL Format / Subdomain** section. 4. Select your preferred style: * **Subdomain:** Generates `https://yourusername.x02.me/i/...` * **Short URL:** Generates `https://x02.me/u/...` * **Custom Domain:** Generates `https://your-domain.com/i/...` — only shown once you've added and verified a domain in the [Custom Domain section](/blog/branded-file-hosting-custom-subdomains) (Pro plan required). 5. Click **Save Preferences**. Your selected style will now be applied automatically to all future uploads made via the web browser, clipboard paste, ShareX, MCP, and the API. --- ### What Happens to Your Existing Uploads? > [!NOTE] > **Zero Broken Links:** Changing your URL format preference only affects the link copied for **new uploads**. All previously shared files continue to resolve on both domains without interruption. --- ## Part 2: The Developer & Power User Guide For developers building third-party integrations, bots, or custom uploaders, X02 exposes REST endpoints to read and update your URL format settings programmatically. --- ### 1. Checking Your Current URL Format via API Send a `GET` request to `/api/user/subdomain`: ```bash curl -X GET "https://x02.me/api/user/subdomain" \ -H "x-api-key: YOUR_X02_API_KEY" ``` #### JSON Response: ```json { "success": true, "data": { "format": "subdomain", "subdomainUrl": "https://alex.x02.me/i/ABCD.png", "standardUrl": "https://x02.me/u/ABCD.png", "customDomainActive": false, "customDomainUrl": null } } ``` --- ### 2. Updating Your URL Format via API Send a `POST` request to `/api/user/subdomain` with the desired format: #### Set to Subdomain Mode: ```bash curl -X POST "https://x02.me/api/user/subdomain" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "format": "subdomain" }' ``` #### Set to Short URL Mode: ```bash curl -X POST "https://x02.me/api/user/subdomain" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "format": "short" }' ``` #### Set to Custom Domain Mode (Pro plan, domain must already be verified): ```bash curl -X POST "https://x02.me/api/user/subdomain" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "format": "custom" }' ``` > [!NOTE] > Setting `format` to `"custom"` before a domain is added and verified via `/api/user/custom-domain` has no effect on new uploads — X02 automatically falls back to `subdomain` format until your domain shows as **Active**. --- ### 3. JavaScript / TypeScript Implementation (Node.js & Browser) ```typescript async function setUrlFormatPreference(format: "subdomain" | "short", apiKey: string) { const response = await fetch("https://x02.me/api/user/subdomain", { method: "POST", headers: { "x-api-key": apiKey, "Content-Type": "application/json", "Accept": "application/json", }, body: JSON.stringify({ format }), }); if (!response.ok) { const error = await response.json(); throw new Error(`Failed to update URL format: ${error.error}`); } const result = await response.json(); console.log("Updated URL format preference:", result); return result; } // Example usage: Switch to clean short URLs setUrlFormatPreference("short", "YOUR_API_KEY") .catch(console.error); ``` --- ### 4. Python Implementation ```python import requests def update_url_format(format_type: str, api_key: str): url = "https://x02.me/api/user/subdomain" headers = { "x-api-key": api_key, "Content-Type": "application/json", "Accept": "application/json" } payload = {"format": format_type} response = requests.post(url, headers=headers, json=payload) if response.status_code == 200: data = response.json() print("URL format updated successfully:", data) return data else: print(f"Error ({response.status_code}):", response.text) response.raise_for_status() # Example usage update_url_format("subdomain", "YOUR_API_KEY") ``` --- ### 3. How Subdomain Routing Works Under the Hood When a request arrives at `https://username.x02.me/i/file.png`: 1. **Wildcard DNS Resolution:** Cloudflare Anycast DNS captures `*.x02.me` traffic and routes it to X02's edge gateway. 2. **Wildcard Edge SSL:** Automatic TLS/SSL certificates secure all tenant subdomains with zero manual certificate provisioning. 3. **Tenant Context Extraction:** The edge server parses the `Host` header, extracts the username prefix, validates tenant ownership, and routes the asset directly from object storage (R2/S3). 4. **Isolated Brand Experience:** Viewers receive direct, high-speed media delivery branded with your personal handle. --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **Set up a Custom Domain (Pro)** | [Custom Domain Setup Guide ](/blog/branded-file-hosting-custom-subdomains) | | **Configure ShareX to use your subdomain** | [How to Integrate ShareX with X02 ](/blog/how-to-integrate-sharex-with-x02) | | **Organize uploads into custom folders** | [How to Organize Files with Folders ](/blog/how-to-organize-files-with-folders) | | **Swap uploaded files without changing links** | [How to Replace a File Without Changing Its Link ](/blog/how-to-replace-file-without-changing-link) | | **Give your uploads clean custom names** | [How to Rename Your Uploaded Files ](/blog/how-to-rename-uploaded-files) | --- # How to Upload Files to X02 Source: https://x02.me/blog/how-to-upload-files-to-x02 2026-09-01 · X02 Team Sharing files on the internet shouldn't require creating complex accounts, waiting through countdown timers, or navigating ad-filled download pages. **X02** is built to do one thing exceptionally well: host your files securely and give you an instant, blazing-fast direct link you can share anywhere. Whether you're quickly sharing a screenshot with a friend on Discord or automating build artifact uploads across continuous integration (CI/CD) pipelines, X02 supports a variety of upload methods. > [!TIP] > **Quick Navigation:** > * [Browser Drag & Drop](#1-drag-and-drop-in-your-web-browser) > * [Clipboard Instant Paste (Ctrl+V)](#2-paste-directly-from-your-clipboard-ctrl--v--cmd--v) > * [Auto-Expiring / Self-Destructing Uploads](#3-setting-auto-expiring-self-destructing-links) > * [Upload Limits & Supported File Formats](#4-upload-limits--supported-file-types) > * [Terminal & Developer cURL / API](#1-uploading-via-curl-terminal) > * [Desktop Screen Capture (ShareX)](#6-desktop-screen-capture-integration-sharex--flameshot) > * [Next Steps & Related Tutorials](#next-steps--related-tutorials) --- ## Part 1: The Easy Guide (For Everyone) You don't need to write code or configure complex software to use X02. Here are the fastest ways to share files right from your browser. ![Architecture Diagram](/blog-diagrams/how-to-upload-files-to-x02-1.svg) ### 1. Drag-and-Drop in Your Web Browser | Where to Go | Direct Link | Action | | :--- | :--- | :--- | | **`Homepage`** | [Open x02.me](https://x02.me) | Drag any file into the dropzone | 1. Open **[x02.me](https://x02.me)** in your browser. 2. Drag your image, video, document, or archive file from your computer and drop it into the upload box. 3. Your file uploads instantly, and your direct shareable link (`https://x02.me/i/yourfile.png`) is copied straight to your clipboard. --- ### 2. Paste Directly from Your Clipboard (`Ctrl + V` / `Cmd + V`) If you take a screenshot using your system's built-in snipping tool (like `Win + Shift + S` on Windows or `Cmd + Shift + 4` on macOS), you don't even need to save the file to your computer first: 1. Copy or capture any image to your clipboard. 2. Open **[x02.me](https://x02.me)**. 3. Press **`Ctrl + V`** (or **`Cmd + V`** on Mac). 4. The image will upload immediately and return your direct link. > [!TIP] > **Clipboard Snipping Pro-Tip:** You don't have to click on the upload box first—just press `Ctrl + V` anywhere on the page! --- ### 3. Setting Auto-Expiring (Self-Destructing) Links Sometimes you need to share a file temporarily—like a sensitive document, a short-lived bug screenshot, or a private draft—and you don't want it lingering on the web forever. Before dropping or selecting your file, use the **Expiry Selector** dropdown: | Expiry Option | What Happens | Best For | |---|---|---| | **Never (Default)** | File stays hosted permanently | Public screenshots, website assets, portfolio items | | **1 Hour** | Auto-deleted after 60 minutes | One-off sensitive snippets, short discussions | | **6 Hours** | Auto-deleted after 6 hours | Daily work handoffs, temporary review proofs | | **1 Day (24h)** | Auto-deleted after 24 hours | Bug reports, temporary test builds | | **7 Days** | Auto-deleted after 1 week | Weekly collaboration drafts | | **30 Days** | Auto-deleted after 1 month | Monthly invoice previews, temporary project files | --- ### 4. Upload Limits & Supported File Types | User Tier | Max File Size | Hourly Limit | Account Benefits | |---|---|---|---| | **Anonymous (No Login)** | **50 MB** | 30 uploads / hour | Instant sharing, auto-expiry options | | **Registered (Free Account)** | **300 MB** | Custom / High Limit | Personal dashboard, custom subdomain, folders, file management | #### Supported Formats: * **Images:** JPEG (`.jpg`), PNG (`.png`), GIF (`.gif`), WebP (`.webp`), AVIF (`.avif`), SVG (`.svg`), BMP (`.bmp`) * **Videos:** MP4 (`.mp4`), WebM (`.webm`), MOV (`.mov`), MKV (`.mkv`), AVI (`.avi`) * **Audio:** MP3 (`.mp3`), WAV (`.wav`), FLAC (`.flac`), OGG (`.ogg`), M4A (`.m4a`) * **Documents & Archives:** PDF (`.pdf`), Word (`.docx`), Excel (`.xlsx`), Text (`.txt`), ZIP (`.zip`), RAR (`.rar`), 7z (`.7z`) --- ## Part 2: The Developer & Power User Guide For engineers, automation pipelines, and AI assistants, X02 provides a high-throughput REST API, an MCP server, and desktop integration endpoints. --- ### 1. Uploading via cURL (Terminal) You can send files to X02 directly from your command line using `curl`: #### Anonymous Upload (Single Command) ```bash curl -X POST "https://x02.me/api/upload" \ -F "file=@screenshot.png" ``` #### Authenticated Upload with Expiration If you have an account, generate an API key in your **Dashboard Settings** to associate uploads with your user account: ```bash curl -X POST "https://x02.me/api/upload" \ -H "x-api-key: YOUR_X02_API_KEY" \ -F "file=@build-report.pdf" \ -F "expiry=1d" ``` #### Requesting Structured JSON Output By default, X02 returns a raw plaintext URL string. If you are building a tool or script, send the `Accept: application/json` header to receive a structured JSON object: ```bash curl -X POST "https://x02.me/api/upload" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Accept: application/json" \ -F "file=@diagram.png" ``` **Response Payload:** ```json { "success": true, "url": "https://x02.me/i/K8j2Pq.png", "filename": "K8j2Pq.png", "size": 248102, "mimeType": "image/png", "expiry": "1d", "deleteUrl": "https://x02.me/api/user/images/K8j2Pq.png/delete" } ``` --- ### 2. Uploading in JavaScript / TypeScript (Node.js & Browser) Here is a modern `fetch` implementation uploading a file via standard `FormData`: ```typescript import fs from "fs"; async function uploadToX02(filePath: string, apiKey?: string): Promise { const formData = new FormData(); const fileBlob = new Blob([fs.readFileSync(filePath)]); formData.append("file", fileBlob, "sample.png"); formData.append("expiry", "7d"); // Optional: 1h, 6h, 1d, 7d, 30d const headers: Record = { Accept: "application/json", }; if (apiKey) { headers["x-api-key"] = apiKey; } const response = await fetch("https://x02.me/api/upload", { method: "POST", headers, body: formData, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Upload failed (${response.status}): ${errorText}`); } const data = await response.json(); return data.url; // e.g. https://x02.me/i/AbCdEf.png } // Example usage uploadToX02("./screenshot.png", "YOUR_API_KEY") .then((url) => console.log("Hosted at:", url)) .catch(console.error); ``` --- ### 3. Uploading in Python A lightweight Python snippet using the `requests` library: ```python import requests def upload_file(file_path: str, api_key: str = None, expiry: str = None) -> str: url = "https://x02.me/api/upload" headers = {"Accept": "application/json"} if api_key: headers["x-api-key"] = api_key data = {} if expiry: data["expiry"] = expiry with open(file_path, "rb") as f: files = {"file": f} response = requests.post(url, headers=headers, files=files, data=data) response.raise_for_status() return response.json().get("url") # Example usage hosted_url = upload_file("debug-log.txt", api_key="YOUR_API_KEY", expiry="1d") print("Direct URL:", hosted_url) ``` --- ### 4. Uploading Remote URLs Directly (`/api/upload/url`) If you want X02 to fetch and host a file from an existing internet URL without downloading it to your local machine first, use the remote URL ingestion endpoint: ```bash curl -X POST "https://x02.me/api/upload/url" \ -H "x-api-key: YOUR_X02_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/external-graphic.png", "expiry": "30d" }' ``` --- ### 5. Let an AI Assistant Upload for You (MCP) X02 runs a [Model Context Protocol](https://modelcontextprotocol.io) server, so AI coding assistants and chat clients can upload and manage files on your account directly. You sign in once in the browser and approve what the app is allowed to do — your API key is never pasted into the client. ```bash # Claude Code claude mcp add --transport http x02 https://up.x02.me/mcp # Any other MCP client (Claude Desktop, Cursor, Windsurf): use this URL https://up.x02.me/mcp ``` Once connected, you can just ask: > "Upload this screenshot to X02 and give me the link." > "Move last week's uploads into a folder called Archive." The server exposes twelve tools covering uploads, search, renaming, moving, deleting, and folder sharing. Access is scoped — an app that only asked to read your files cannot delete them — and you can disconnect any client without rotating your API key. > [!NOTE] > `upload_file` sends bytes base64-encoded inside the tool call, so it only suits genuinely small files — an ordinary screenshot becomes ~200,000 characters and most assistants will not emit that. Use `upload_from_url` when the file is already online, or upload it here and hand the assistant the link. --- ### 6. Desktop Screen Capture Integration (ShareX & Flameshot) You can connect desktop screenshot tools so that pressing your hotkey instantly captures a screen region, uploads it to X02, and puts the link in your clipboard: * **ShareX (Windows):** Download your pre-configured `.sxcu` file from your **Dashboard -> Settings -> Integrations** and double-click to install. * **Flameshot (Linux / macOS):** Point your post-capture script at the upload endpoint — `curl -s -H "x-api-key: YOUR_API_KEY" -F "file=@$1" https://up.x02.me/api/upload | xclip -selection clipboard`. --- ## Next Steps & Related Tutorials | What to learn next | Guide Link | | :--- | :--- | | **Set up 1-second desktop screenshot hotkeys** | [How to Integrate ShareX with X02 ](/blog/how-to-integrate-sharex-with-x02) | | **Organize your uploads into folders** | [How to Organize Files with Folders ](/blog/how-to-organize-files-with-folders) | | **Update or replace files without breaking links** | [How to Replace a File Without Changing Its Link ](/blog/how-to-replace-file-without-changing-link) | | **Give your uploads clean custom names** | [How to Rename Your Uploaded Files ](/blog/how-to-rename-uploaded-files) | --- # Building High-Throughput Discord Support & Ticket Infrastructure with X02 Source: https://x02.me/blog/building-discord-ticket-infrastructure 2026-08-28 · X02 Team As gaming communities, SaaS tools, and decentralized projects scale past tens of thousands of members, community support quickly collapses if relegated to public text channels or raw direct messages. Moderators become overwhelmed with duplicate questions, sensitive payment or account inquiries leak into public view, and accountability vanishes without audit trails. To solve this, modern Discord server architectures rely on **dedicated ticket lifecycle systems**. In this guide, we dive deep into how X02's Discord ticket infrastructure operates under the hood, how to design scalable permission hierarchies, and how automated HTML transcripts guarantee long-term operational accountability. --- ## 1. The Discord Ticket Lifecycle Architecture A production-grade Discord ticket system is fundamentally a state machine that synchronizes Discord API channel permissions, database tracking, and web socket events. ![Architecture Diagram](/blog-diagrams/building-discord-ticket-infrastructure-1.svg) ### Key Lifecycle Phases 1. **Panel Dispatch**: Embeds with dynamic button and select menu components are deployed to target channels. 2. **Atomic Channel Provisioning**: The bot provisions a private channel with tailored permission overwrites (`@everyone` denied `ViewChannel`, ticket creator + support roles granted `ViewChannel`, `SendMessages`, `AttachFiles`). 3. **Staff Triage & Claiming**: Staff members claim tickets to lock concurrency and avoid duplicate responses. 4. **Snippet Acceleration**: Canned responses allow instant resolution of recurring inquiries. 5. **Transcribing & Teardown**: Upon resolution, the channel message history is compiled into a standalone, tamper-proof HTML transcript, logged to an archive channel, and deleted from Discord to avoid hitting server channel limits (500 maximum channels per guild). --- ## 2. Designing Scalable Permission Hierarchies One of the most frequent points of failure in Discord bots is permission race conditions. If permissions are misconfigured, regular members might see tickets created by other users, creating critical privacy breaches. ### Recommended Role & Overwrite Matrix When setting up your ticket categories (e.g., *Billing*, *Technical Support*, *Bug Bounty*), enforce strict overwrite isolation: ```javascript // Example: Discord.js permission overwrite payload for ticket creation const channelOverwrites = [ { id: guild.id, // @everyone role deny: ['ViewChannel', 'SendMessages', 'ReadMessageHistory'], }, { id: userId, // Ticket Creator allow: [ 'ViewChannel', 'SendMessages', 'ReadMessageHistory', 'AttachFiles', 'EmbedLinks' ], }, { id: supportRoleId, // Designated Tier-1 Support Role allow: [ 'ViewChannel', 'SendMessages', 'ReadMessageHistory', 'AttachFiles', 'ManageMessages' ], }, { id: botClientId, // The Ticket Bot allow: [ 'ViewChannel', 'SendMessages', 'ManageChannels', 'ManageMessages', 'ReadMessageHistory' ], } ]; ``` > [!TIP] > Always place ticket channels inside dedicated Discord Categories that match your maximum concurrency forecast. Discord allows up to 50 channels per category, so high-volume servers should cycle through rotating category pools. --- ## 3. High-Fidelity Automated HTML Transcripts When resolving disputes, chargebacks, or community guideline violations, raw text dumps lack context. X02 features an automated transcript generator that renders Discord-native visual HTML transcripts including: - Full Discord theme styling (Dark mode with precise CSS color tokens) - User avatars, bot badges, custom emojis, and role color accents - Embedded images, videos, audio players, and code blocks - Interactive timestamps (hoverable tooltips showing UTC ISO timestamps) ### How Transcripts are Generated Securely 1. When a close command or button is triggered, the system fetches message history in batches of 100 via REST. 2. Attachment media URLs are verified and re-hosted to permanent storage if needed. 3. Message markdown is parsed into sanitized HTML using DOMPurify to eliminate XSS vectors. 4. The generated HTML payload is uploaded and made accessible via password-protected or signed URLs. ```html
Avatar
Alex (Senior Support) 28-Aug-2026 14:32
Your API quota has been replenished. Please verify your endpoints.
``` --- ## 4. Triage Workflows: Moderator Claiming & Auto-Close Efficiency in customer support directly correlates with First Response Time (FRT) and Mean Time to Resolution (MTTR). ### 1. The Claim Mechanism When multiple support staff are online, two agents might start typing answers simultaneously. With X02's **Claiming System**: - An agent clicks **Claim Ticket**. - The bot updates the channel topic, renames the channel to `claimed-user123`, and restricts write permissions so other staff cannot conflict unless they explicitly click **Unclaim** or **Escalate**. ### 2. Auto-Close for Stale Inquiries Unanswered tickets consume channel slots indefinitely. X02's background scheduler monitors ticket activity: - After **24 hours of inactivity**, an automated warning embed is sent with a reminder. - After **48 hours without user response**, the ticket is automatically archived with a full transcript delivered to the user's direct messages. --- ## 5. Integrating with the X02 Web Dashboard While Discord slash commands provide in-situ moderation, high-level administration requires web-based oversight. Through the [X02 Tickets Dashboard](https://x02.me/tickets): - **Real-Time Analytics**: Monitor open vs. closed ratios, response latency graphs, and staff performance metrics. - **Visual Panel Builder**: Design multi-tier embeds with real-time color pickers, category binding, and layout management without touching JSON configuration. - **Snippet Library**: Maintain global and server-scoped response templates with variable interpolation (`{user}`, `{server}`, `{staff}`). ```javascript // Using X02 API to programmatically deploy a ticket panel const response = await fetch("https://x02.me/api/discord/panels", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": process.env.X02_API_KEY }, body: JSON.stringify({ serverId: "1057573344855207966", channelId: "123456789012345678", title: "Technical Support & Inquiries", description: "Click below to connect directly with our engineering staff.", buttonLabel: "Open Ticket", themeColor: "#FF2A55" }) }); ``` --- ## Conclusion A modern Discord community cannot rely on ad-hoc direct messages or cluttered channels. By deploying structured ticket lifecycles, automated HTML transcripts, and proactive inactivity handling, your moderation team can support hundreds of thousands of users with enterprise reliability. Ready to upgrade your server support? Get started with the **[X02 Discord Ticket System](https://x02.me/tickets)** in less than two minutes. --- # Architecture of Real-Time NSFW Detection & Quarantine at Scale Source: https://x02.me/blog/realtime-nsfw-detection-at-scale 2026-08-27 · X02 Team Content moderation is one of the most demanding challenges for high-volume file hosting providers, media sharing APIs, and online communities. With thousands of files uploaded per minute, human moderation alone is fundamentally unscalable, prone to severe latency bottlenecks, and exposes human reviewers to psychological strain. At X02, we developed **X02-Vision AI** — an asynchronous, distributed image and media classification pipeline designed to detect Not Safe For Work (NSFW) imagery, graphic violence, and illegal media within milliseconds of ingestion without slowing down end-user upload speeds. In this article, we break down the engineering architecture behind X02-Vision AI, our multi-tiered classification heuristic, and how automated quarantine systems isolate threats safely. --- ## 1. System Overview & The Upload Pipeline When an asset is uploaded through the X02 Web Dashboard, ShareX integration, or REST API, it passes through a multi-stage ingestion pipeline: ![Architecture Diagram](/blog-diagrams/realtime-nsfw-detection-at-scale-1.svg) ### The Ingestion Bottleneck: Asynchronous vs. Synchronous Scanning A common architectural mistake is making uploads wait synchronously on heavy deep learning inference. If a 10MB raw image triggers a heavy PyTorch model run on the request thread, latency climbs to over 1.5 seconds, causing client timeouts. **X02 uses a hybrid pipeline:** 1. **Lightweight In-Memory Pre-Scan**: A memory-mapped buffer is normalized using `libvips`/`sharp` to a fixed 224x224 tensor. 2. **Fast-Pass Inference**: The tensor runs through an optimized lightweight ONNX runtime engine running on dedicated GPU/NPU inference workers, completing in under **45 milliseconds**. 3. **Optimistic Storage with Instant Revocation**: If the confidence score indicates safe content (`P(Safe) > 0.95`), the upload response returns instantly. If the confidence falls into suspect thresholds, the file is immediately quarantined before edge CDN caches propagate. --- ## 2. Multi-Class Output Probabilities Unlike primitive threshold binary checks (safe vs. unsafe), X02-Vision evaluates a multi-dimensional probability distribution: | Category | Description | Trigger Threshold | |---|---|---| | **P(Pornography / Explicit)** | Overt sexual acts, explicit nudity | `> 0.65` (Instant Quarantine) | | **P(Suggestive / Sexy)** | Revealing clothing, artistic photography | `> 0.85` (Soft Flag for Review) | | **P(Graphic Violence / Gore)** | Blood, physical trauma, weapons in combat | `> 0.60` (Instant Quarantine) | | **P(Neutral / Safe)** | Standard photographs, landscapes, documents | `> 0.90` (Immediate Approval) | ```javascript // Example: Output payload from the X02-Vision Inference Service { "fileId": "img_98xfa8201a", "mimeType": "image/jpeg", "latencyMs": 42.8, "scores": { "neutral": 0.982, "suggestive": 0.014, "explicit": 0.003, "violence": 0.001 }, "verdict": "APPROVED", "quarantine": false } ``` --- ## 3. Automated Quarantine & Triage Workflow When an asset triggers quarantine: 1. **Edge URL Revocation**: The CDN cache key is invalidated via edge purge API within 250ms. 2. **Encrypted Isolation**: The file is moved from public object storage to an encrypted, access-restricted quarantine vault. 3. **Admin Alerting**: Telemetry metrics update real-time dashboards for administrative oversight. --- ## Summary Protecting platforms from abusive and illegal content requires defense-in-depth engineering. By pairing memory-safe in-flight streaming with sub-50ms tensor inference and automated quarantine isolation, X02 maintains a safe hosting environment without compromising performance. --- # Why Discord CDN URLs Expire and How to Build a Permanent Media Rehosting Pipeline Source: https://x02.me/blog/discord-cdn-expiration-permanent-media 2026-08-25 · X02 Team In late 2023, Discord announced a massive architectural shift in how it serves user attachments. Starting in early 2024, all direct media links hosted on `cdn.discordapp.com` and `media.discordapp.com` were upgraded to enforce **ephemeral HMAC signature tokens**. For thousands of developer projects, Discord bots, community wikis, and forum embeds that used Discord as a makeshift free Content Delivery Network (CDN), this change caused broken images, 403 Forbidden errors, and corrupted user archives worldwide. In this article, we explain the mechanics behind Discord's signed expiration URLs, why using Discord as a CDN was always flawed, and how to construct an automated media rehosting pipeline using X02's API to ensure your attachments remain permanent forever. --- ## 1. Deconstructing Discord's Ephemeral URL Signature Historically, Discord attachment URLs followed a static, predictable structure: `https://cdn.discordapp.com/attachments/123456789/987654321/image.png` Under the updated security enforcement, all URLs generated within Discord channels now include mandatory authentication query parameters: ``` https://cdn.discordapp.com/attachments/1057573344855207966/1234567890/asset.png?ex=66d1f08a&is=66d09f0a&hmac=a9f8b7c6d5e4... ``` ### The Three Query Parameters: 1. **`ex` (Hexadecimal Timestamp)**: The exact Unix expiration timestamp when the link becomes invalid (typically 24 hours from link generation). 2. **`is` (Hexadecimal Timestamp)**: The link issuance timestamp when Discord's edge generated the signed string. 3. **`hmac` (Cryptographic Signature)**: A SHA-256 HMAC hash signed by Discord's private server key across the path and timestamps. Once `Date.now() > ex`, attempting to fetch or embed that image outside of active Discord clients returns: ```http HTTP/1.1 403 Forbidden Content-Type: text/plain Access Denied: Signature has expired. ``` > [!WARNING] > While active Discord desktop and mobile apps refresh signatures in real-time when rendering message channels, any link copied outside Discord (such as a database record, blog post, external dashboard, or third-party website) will break permanently after 24 hours. --- ## 2. The Solution: Automated Ingestion & Permanent Rehosting To ensure that images, bug report attachments, user submissions, and receipts remain accessible indefinitely, your backend or bot must implement an **intercept-and-rehost pipeline**. ![Architecture Diagram](/blog-diagrams/discord-cdn-expiration-permanent-media-1.svg) --- ## 3. Implementation in Node.js (Discord.js v14) Here is a production-tested snippet using `discord.js` that automatically intercepts incoming user attachments and re-hosts them to X02: ```typescript import { Client, GatewayIntentBits, Message } from "discord.js"; import FormData from "form-data"; import fetch from "node-fetch"; const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, ], }); const X02_API_KEY = process.env.X02_API_KEY!; const X02_UPLOAD_URL = "https://x02.me/api/upload"; /** * Downloads ephemeral Discord attachment and re-uploads to permanent X02 storage. */ async function rehostToX02(ephemeralUrl: string, originalFilename: string): Promise { try { // 1. Fetch binary stream from Discord const discordResponse = await fetch(ephemeralUrl); if (!discordResponse.ok) throw new Error(`Discord download failed: ${discordResponse.statusText}`); const buffer = await discordResponse.buffer(); // 2. Build multipart payload const form = new FormData(); form.append("file", buffer, { filename: originalFilename }); // 3. Dispatch to X02 const x02Response = await fetch(X02_UPLOAD_URL, { method: "POST", headers: { "x-api-key": X02_API_KEY, ...form.getHeaders(), }, body: form, }); const data: any = await x02Response.json(); if (data.success && data.url) { return data.url; // Guaranteed permanent URL } return null; } catch (error) { console.error("Rehosting error:", error); return null; } } client.on("messageCreate", async (message: Message) => { if (message.author.bot) return; // Intercept images / attachments if (message.attachments.size > 0) { for (const [, attachment] of message.attachments) { const permanentUrl = await rehostToX02(attachment.url, attachment.name); if (permanentUrl) { console.log(`Successfully preserved media: ${permanentUrl}`); // Store permanentUrl into your PostgreSQL / Prisma database here } } } }); client.login(process.env.DISCORD_BOT_TOKEN); ``` --- ## 4. Implementation in Python (discord.py / aiohttp) For Python developers, here is the async equivalent using `discord.py` and `aiohttp`: ```python import os import aiohttp import discord from discord.ext import commands intents = discord.Intents.default() intents.message_content = True bot = commands.Bot(command_prefix="!", intents=intents) X02_API_KEY = os.getenv("X02_API_KEY") async def rehost_to_x02(session: aiohttp.ClientSession, attachment_url: str, filename: str) -> str: async with session.get(attachment_url) as resp: if resp.status != 200: return None file_bytes = await resp.read() data = aiohttp.FormData() data.add_field('file', file_bytes, filename=filename) headers = {"x-api-key": X02_API_KEY} async with session.post("https://x02.me/api/upload", data=data, headers=headers) as resp: res = await resp.json() if res.get("success"): return res.get("url") return None @bot.event async def on_message(message): if message.author.bot: return if message.attachments: async with aiohttp.ClientSession() as session: for att in message.attachments: permanent_link = await rehost_to_x02(session, att.url, att.filename) print(f"Rehosted to X02: {permanent_link}") await bot.process_commands(message) bot.run(os.getenv("DISCORD_TOKEN")) ``` --- ## Summary Discord's shift to ephemeral CDN URLs reinforces a critical lesson in cloud architecture: **never use communication platforms as persistent data stores**. By integrating an automated rehosting layer into your bot workflows, you ensure data permanence, retain control over your media assets, and protect your user interfaces from decaying 403 errors. --- # Branded File Hosting: Configuring Your Custom Domain & Edge SSL Source: https://x02.me/blog/branded-file-hosting-custom-subdomains 2026-08-24 · X02 Team When sharing screenshots, software builds, or media assets with customers and communities, URL presentation matters. A generic file-sharing link like `https://share-host-xyz.io/tmp/82910fa.png` looks unverified, risks triggering corporate phishing filters, and misses a prime brand touchpoint. In contrast, sharing links through **`https://img.yourcompany.com/report_q3.png`** or **`https://files.alex.dev/demo.mp4`** establishes instant trust, protects brand equity, and reinforces developer credibility. > [!NOTE] > **This is a Pro plan feature.** Every account already gets a free, zero-setup personal subdomain (`https://yourusername.x02.me/i/...`) — see [How to Switch Between Short URLs and Subdomains](/blog/how-to-switch-between-short-urls-and-subdomains) for that. This guide covers **Custom Domain**, which lets Pro users serve files from a domain they personally own instead. In this guide, we explore how custom domain routing works on X02, how to configure the required DNS records across popular DNS providers, and how automated TLS/SSL certificates are provisioned at the edge. --- ## 1. How Custom Domain Routing Operates When a client requests an image from your custom domain, the request travels through X02's Cloudflare edge network before resolving the asset: ![Architecture Diagram](/blog-diagrams/branded-file-hosting-custom-subdomains-1.svg) ### Why This Requires Three DNS Records, Not One Cloudflare needs to prove two separate things before it will route your domain's traffic and issue it a certificate: that you actually **own** the domain (an ownership TXT record), and that the **SSL certificate** can be validated (a separate `_acme-challenge` TXT record). Only once both checks pass does Cloudflare start routing the CNAME to X02's edge. Skipping either TXT record is the most common reason a custom domain gets stuck on "Pending." --- ## 2. DNS Configuration Step-by-Step Setting up your custom domain requires adding **three** DNS records at your domain registrar or DNS provider. All three are generated for you automatically when you add the domain in your dashboard — you just copy them across. ### Configuration Targets: | # | Record Type | Purpose | Target / Value | |---|---|---|---| | 1 | **CNAME** | Routes traffic to X02's edge | `x02.me` | | 2 | **TXT** | Proves domain ownership to Cloudflare | Unique value shown in your dashboard | | 3 | **TXT** (`_acme-challenge.*`) | Validates the SSL certificate | Unique value shown in your dashboard | > [!TIP] > Your dashboard shows the exact Name/Host and Value for all three records, pre-filled for the specific domain you add — there's nothing to guess or copy from this guide. --- ## 3. Provider Setup Guides ### 1. Cloudflare DNS 1. Log in to your Cloudflare Dashboard and select your domain. 2. Navigate to **DNS** > **Records** > **Add Record**. 3. Add the **CNAME** record: **Name** = your chosen prefix (e.g. `img`), **Target** = `x02.me`, **Proxy Status** = **DNS Only** (Grey Cloud) — do not proxy it, or Cloudflare cannot validate ownership through your own zone. 4. Add both **TXT** records exactly as shown in your X02 dashboard (Name and Value, copied verbatim). 5. Click **Save** for each record. ### 2. Namecheap / GoDaddy / Porkbun 1. Open your domain's Advanced DNS Management panel. 2. Add a **CNAME Record**: **Host** = your chosen prefix (e.g. `img`), **Value / Target** = `x02.me`. 3. Add both **TXT Records** shown in your dashboard, using the exact Host/Name and Value fields provided. 4. Set TTL to `Automatic` or `1 Hour` and save. --- ## 4. Verifying & Activating Your Domain Once your records propagate (usually 5–15 minutes, occasionally longer depending on your DNS provider): 1. Head to your **[X02 Settings Dashboard](https://x02.me/dashboard)**. 2. In the **Custom Domain** section, enter your full domain (e.g., `img.yourbrand.com`) and click **Add Domain**. 3. Add the three DNS records shown to your provider as described above. 4. The dashboard automatically re-checks your DNS every 20 seconds and shows a live **Verified** / **Pending** status per record — or click **Verify Now** to force an immediate check. 5. Once all three records resolve, X02 issues a managed SSL certificate for your domain automatically and activates routing. ```bash # Verify your DNS propagation via Terminal dig CNAME img.yourbrand.com +short # Output should return: x02.me ``` --- ## 5. ShareX Integration with Branded URLs After activating your custom domain in the X02 dashboard, your generated **ShareX Custom Uploader (.sxcu)** configuration automatically reflects your brand domain: ```json { "Version": "15.0.0", "Name": "My Branded Uploader", "DestinationType": "ImageUploader, FileUploader", "RequestMethod": "POST", "RequestURL": "https://x02.me/api/upload", "Headers": { "x-api-key": "x02_sec_YOUR_API_KEY" }, "Body": "MultipartFormData", "FileFormName": "file", "URL": "https://img.yourbrand.com/$json:filename$" } ``` Every screenshot you capture and hotkey-upload now copies a clean, branded link directly to your clipboard. --- ## Summary A custom domain transforms standard file hosting into a seamless extension of your company or creator identity. With automated DNS verification and edge SSL termination handled for you, setting up your branded file hub on X02's Pro plan takes only a few minutes of DNS configuration plus propagation time. --- # Architecting High-Performance File Uploads: Chunking, Streams, and S3-Compatible Storage Source: https://x02.me/blog/high-performance-file-uploads-architecture 2026-08-22 · X02 Team Handling file uploads in modern web applications sounds deceptively simple until you encounter real-world constraints: users uploading 500MB video files over unstable 4G mobile connections, sudden spikes in concurrent traffic overwhelming Node.js memory heaps, and server timeouts dropping uploads at 99% progress. At X02, our ingestion infrastructure processes millions of uploads globally with 99.99% reliability. In this article, we dissect the architectural principles behind high-throughput upload systems, compare **streaming proxy architectures** against **direct presigned S3/R2 uploads**, and examine resilient client-side retry strategies. --- ## 1. Upload Architectures: Monolithic vs. Presigned Direct-to-Storage When designing a cloud file uploader, there are two primary architectural paradigms: ![Architecture Diagram](/blog-diagrams/high-performance-file-uploads-architecture-1.svg) ### How X02 Implements a Hybrid High-Performance Ingestion Layer X02 combines the best of both approaches: - **Every upload streams** through `Busboy`/`Multer` straight into Cloudflare R2, so the server never holds a whole file in memory. Images are copied to disk as they pass through, and X02-Vision AI scans them right after the upload completes. - **Files over 5MB (up to 512MB on Pro)** go to R2 as **Chunked Multipart Uploads**: the stream is cut into 5MB parts, two in flight at a time, and R2 joins them into one object when the last part lands. --- ## 2. Chunked Multipart Uploads & Resumability Uploading a 300MB archive as a single monolithic HTTP request is fragile. If the user's connection drops for 200ms at megabyte 290, the entire 290MB is lost and must restart from zero. ### Chunked Upload Protocol Mechanics: 1. **Initialize**: Client calculates total file size and hashes the file's first block. 2. **Partition**: The file is sliced into uniform chunks using `File.prototype.slice()` in the browser. 3. **Concurrent Upload**: Up to 3-5 chunks are transmitted simultaneously. 4. **Idempotent Retry**: If Chunk #4 fails due to network failure, only Chunk #4 retries with exponential backoff. 5. **Complete**: When all parts are confirmed, an atomic commit command stitches the parts into a single object. ```javascript // Browser client-side chunked upload algorithm async function uploadLargeFileInChunks(file, uploadEndpoint, apiKey) { const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB chunks const totalChunks = Math.ceil(file.size / CHUNK_SIZE); const uploadId = crypto.randomUUID(); console.log(`Starting chunked upload: ${totalChunks} chunks for ${file.name}`); for (let index = 0; index < totalChunks; index++) { const start = index * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); const chunkBlob = file.slice(start, end); const formData = new FormData(); formData.append("chunk", chunkBlob); formData.append("uploadId", uploadId); formData.append("chunkIndex", index.toString()); formData.append("totalChunks", totalChunks.toString()); formData.append("filename", file.name); let retries = 3; while (retries > 0) { try { const res = await fetch(`${uploadEndpoint}/chunk`, { method: "POST", headers: { "x-api-key": apiKey }, body: formData }); if (!res.ok) throw new Error(`Chunk ${index} failed`); break; // Success } catch (err) { retries--; if (retries === 0) throw err; await new Promise(r => setTimeout(r, 1000 * Math.pow(2, 3 - retries))); } } } // Finalize upload const finishRes = await fetch(`${uploadEndpoint}/finalize`, { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": apiKey }, body: JSON.stringify({ uploadId, filename: file.name, totalChunks }) }); return finishRes.json(); } ``` --- ## 3. Backpressure & Memory-Safe Node.js Streams A frequent cause of production Out-Of-Memory (OOM) crashes in Node.js backends is buffering entire uploaded files in RAM: ```typescript // Anti-Pattern: Buffering entire 200MB file into V8 heap RAM const buffer = await req.file.buffer; // 200 concurrent users will crash the process (40GB RAM) ``` ### The Solution: Direct Stream Piping with Backpressure By piping the incoming request stream directly into the S3 multipart upload stream, memory consumption stays **flat regardless of file size**: the SDK only holds the parts it is sending, about `(queueSize + 1) × partSize`. With the settings below that is roughly 25MB per upload, whether the file is 50MB or 5GB. (X02 runs two parts in flight, about 15MB of buffers, around 20–30MB per upload in practice.) ```typescript // High Performance: Stream piping with minimal memory overhead import { PassThrough } from "stream"; import { Upload } from "@aws-sdk/lib-storage"; import { S3Client } from "@aws-sdk/client-s3"; async function streamUploadToR2(reqStream: NodeJS.ReadableStream, key: string, mime: string) { const passThrough = new PassThrough(); reqStream.pipe(passThrough); const parallelUploads3 = new Upload({ client: s3Client, params: { Bucket: process.env.R2_BUCKET_NAME, Key: key, Body: passThrough, ContentType: mime, }, queueSize: 4, partSize: 5 * 1024 * 1024, leavePartsOnError: false, }); parallelUploads3.on("httpUploadProgress", (progress) => { console.log(`Piped ${progress.loaded} bytes to storage.`); }); return parallelUploads3.done(); } ``` --- ## 4. Token Bucket Rate Limiting for Upload Protection To prevent Denial of Service (DoS) and API abuse while maintaining smooth burst capabilities for legitimate users, X02 enforces a **Redis-backed Token Bucket** algorithm. ```typescript // Token Bucket rate limiter implementation for uploads export class TokenBucketLimiter { private capacity: number; private refillRate: number; // Tokens per second constructor(capacity = 50, refillRate = 2) { this.capacity = capacity; this.refillRate = refillRate; } async consume(userId: string, tokensRequested = 1): Promise<{ allowed: boolean; remaining: number }> { const key = `rate:upload:${userId}`; const now = Date.now(); // In production, execute atomic Redis Lua script const data = await redis.hgetall(key); let tokens = data.tokens ? parseFloat(data.tokens) : this.capacity; let lastRefill = data.lastRefill ? parseInt(data.lastRefill, 10) : now; // Calculate replenishment const elapsedSeconds = (now - lastRefill) / 1000; tokens = Math.min(this.capacity, tokens + elapsedSeconds * this.refillRate); if (tokens >= tokensRequested) { tokens -= tokensRequested; await redis.hmset(key, { tokens: tokens.toString(), lastRefill: now.toString() }); return { allowed: true, remaining: Math.floor(tokens) }; } return { allowed: false, remaining: Math.floor(tokens) }; } } ``` --- ## Summary Scaling file uploads to millions of daily requests requires moving beyond monolithic synchronous handlers. By employing **direct stream piping**, **chunked client-side resumability**, and **hybrid storage routing**, you can build a file ingestion layer that is both resilient against network degradation and infinitely horizontally scalable.