Slack API upload file: three calls replace files.upload
Slack deprecated files.upload. Call files.getUploadURLExternal, POST the bytes to upload_url, then files.completeUploadExternal with a channel_id.

To upload a file with the Slack API, make three calls: files.getUploadURLExternal with the file's filename and its length in bytes, a POST of the raw bytes to the upload_url it returns, then files.completeUploadExternal with the returned file id and a channel_id to share the file in that channel. The old one-call files.upload is deprecated: Slack's reference page says it would stop functioning and be sunset on November 12, 2025.
Slack facts come from its files.upload, files.getUploadURLExternal, files.completeUploadExternal and Working with files pages; Sume facts come from Run webhooks and Runs and results. All were read on 2026-09-28. Sume has no Slack app or connector: your own server receives Sume's webhook for a finished video and makes the Slack calls. If a link is enough, Slack bot to generate video posts the URL instead of uploading.
Is files.upload deprecated?
Yes. According to Slack's changelog, newly created apps have been unable to call files.upload since May 16, 2024, and the two replacement methods are "more reliable, especially when uploading large files." The files.upload reference page lists a method_deprecated error that points to that changelog.
Slack's SDKs wrap the new sequence in one call: uploadV2 in the Node @slack/web-api package, files_upload_v2 in python-slack-sdk, and FilesUploadV2Request in the Java SDK. Without an SDK, the sequence is three HTTP requests.
How do I upload a file to a channel with the API?
Send the two Web API calls with a token in the Authorization header; the middle request goes to the URL Slack hands you.
- Call
files.completeUploadExternalexactly once. Slack discards the upload if you never call it, and you don't need to wait for Slack to finish processing the file first. - Both Web API methods accept form-encoded bodies. Slack's own example sends
filesas a JSON string inside the form, and the bytes asapplication/octet-stream.
| Step | Send | Get back |
|---|---|---|
1. files.getUploadURLExternal | filename, and length: the file's size in bytes | upload_url and file_id |
2. POST to upload_url | The file as raw bytes or a multipart form | HTTP 200 on success; any other status is a failure |
3. files.completeUploadExternal | files, an array of file ids with optional titles, plus channel_id and an optional initial_comment | The shared file objects. Without channel_id, the file stays private |
// Node 18+. SLACK_BOT_TOKEN needs the files:write scope.
async function slack(method, params) {
const res = await fetch(`https://slack.com/api/${method}`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SLACK_BOT_TOKEN}` },
body: new URLSearchParams(params), // form-encoded
});
const json = await res.json();
if (!json.ok) throw new Error(`${method}: ${json.error}`);
return json;
}
async function uploadToChannel(fileUrl, filename, channelId) {
const src = await fetch(fileUrl); // a public media.sume.com URL
if (!src.ok) throw new Error(`download answered ${src.status}`);
const bytes = Buffer.from(await src.arrayBuffer());
const { upload_url, file_id } = await slack("files.getUploadURLExternal", {
filename, length: String(bytes.length), // exact size in bytes
});
const sent = await fetch(upload_url, { method: "POST", body: bytes,
headers: { "Content-Type": "application/octet-stream" } });
if (sent.status !== 200) throw new Error(`upload_url answered ${sent.status}`);
await slack("files.completeUploadExternal", {
files: JSON.stringify([{ id: file_id, title: filename }]),
channel_id: channelId, initial_comment: "Your video is ready",
});
}Which scopes and limits apply?
- Both new methods need the
files:writescope on a bot or user token, and Slack rates both as Tier 4: 100+ calls per minute. - The bot must be a member of the channel. Otherwise
files.completeUploadExternalanswersnot_in_channel. - Slack's help center says you can add files up to 1GB in size. That page is about adding files in Slack itself; the two method references give no general file-size cap. A workspace can also restrict large uploads, which
files.getUploadURLExternalreports asfile_upload_size_restricted. - A
lengthof 0 is refused withmissing_argument, so measure the bytes you actually have.
How do I post a video my backend generated?
Upload from the server that receives the finished-video webhook, never from a browser: the Slack token and your Sume API key both stay server-side. For a Sume Format run, the flow looks like this:
- Sume sends one signed POST to your
communication.webhook_urlwhen the run completes or fails. Verify the HMAC-SHA256 signature over the raw body, record the event, and answer2xxwithin the 10-second attempt window; Signed webhooks for Sume video runs covers the check. Upload after you answer, because a slow endpoint gets retried. - Dedupe on the envelope's
request_id, which is the same on every retry, so a retried delivery doesn't post the video twice. - Take
payload.primary_output_url, the one output to show, or an entry ofpayload.artifacts[], which carriesurl,content_typeandsize_bytes. Thesemedia.sume.comURLs are durable and public to anyone holding them, so the download needs no API key. - Don't register a Slack URL as Sume's
webhook_url. Sume's POST carries its own run receipt, not a Slack message.
Sources
- Run webhooks
- Runs and results
- Authentication
- Slack: files.upload method (read 2026-09-28)
- Slack: files.getUploadURLExternal method (read 2026-09-28)
- Slack: files.completeUploadExternal method (read 2026-09-28)
- Slack: Working with files (read 2026-09-28)
- Slack changelog: The files.upload method is retiring (read 2026-09-28)
- Slack Help Center: Add files to Slack (read 2026-09-28)
Related posts
More in Integrations
- Strands Agents MCP: connect an agent to Sume's MCP server
Connect a Strands agent to a remote MCP server with MCPClient: Sume's hosted MCP URL, an API-key header, and tool_filters to keep paid tools out.
- Synthesia MCP: turn a script into a video draft in Claude
Synthesia MCP is a hosted server in public beta at mcp.synthesia.io/mcp. It turns a script into a Synthesia video draft from Claude or ChatGPT.
- Teams incoming webhook retired: switch to a Workflows URL
Microsoft set May 18 to 22, 2026 to disable Office 365 connectors in Teams. Create a Workflows webhook URL and POST an Adaptive Card to it instead.
- YouTube Zapier integration: upload a video from a Zap
Zapier's YouTube integration has an Upload Video action. Map a public, direct file URL into its Video field to post a finished video from a Zap.
Written by Sume