# Upload and share images in Slack with files.uploadV2
> Upload images to Slack with files.uploadV2 or share them by URL, and chain SocialCutter so a bot replies with every format ready.
- URL: https://socialcutter.theboomer.dev/en/guides/slack/
- Idioma: en
- Familia: ia
- Actualizado: 2026-09-24
- Palabras clave: Slack, files.uploadV2, files:write, Block Kit, bot, SocialCutter, Python
## What this solves

Slack is where a team shares images before publishing them. A bot can use that moment: someone drops the image into the channel, the bot crops it with a centered crop to each network's dimensions and replies with the ready-made files. The piece that uploads images to Slack is `files.uploadV2`, and the piece that generates the formats is SocialCutter. This guide covers both and how to chain them.

## Requirements: app, bot and scopes

| Scope | What it is for |
|---|---|
| `files:write` | Upload, edit and delete files |
| `chat:write` | Post messages and share by URL |
| `files:read` | Read metadata (`files.info`, `files.list`), only if you need it |

The bot uses an `xoxb-` token. It must be **invited to the channel** where it will post; otherwise you get `not_in_channel`. Official references: https://docs.slack.dev/messaging/working-with-files and https://docs.slack.dev/reference/scopes/files.write.

## Upload a file with files.uploadV2

`files.upload` is retired. In its place Slack asks for the `files.getUploadURLExternal` and `files.completeUploadExternal` sequence. The official libraries wrap it in a "v2" method, so you do not have to orchestrate the two steps by hand:

```python
from slack_sdk import WebClient

client = WebClient(token="xoxb-your-token")

client.files_upload_v2(
    channel="C0123456789",
    file="./instagram-post.jpg",
    title="Instagram 1:1",
    filename="instagram-post.jpg",
    alt_text="Creative for the Instagram feed",
    initial_comment="Format ready for Instagram",
)
```

| Parameter | What it is for |
|---|---|
| `channel` | Channel where the file is shared |
| `file` / `content` | File path or bytes in memory |
| `filename` | Name with extension |
| `title` | Visible file title |
| `initial_comment` | Message that goes with the file |
| `alt_text` | Alternative text for accessibility |

Version note: Slack retired `files.upload` and recommends the two-step sequence, and the behaviour of the file methods has changed over time. Check the current docs in the official changelog before locking in an implementation: https://docs.slack.dev/changelog/2024-04-a-better-way-to-upload-files-is-here-to-stay.

## Share by URL with Block Kit

If you already have a public URL (SocialCutter returns one per destination), you do not need to upload it to Slack: you can display it with an `image` block inside `chat.postMessage`. This path only needs `chat:write`.

```python
client.chat_postMessage(
    channel="C0123456789",
    blocks=[
        {"type": "image", "image_url": out["url"], "alt_text": f"{out['platform']} {out['format']}"},
        {"type": "context", "elements": [{"type": "mrkdwn", "text": f"{out['width']}x{out['height']} ready to publish"}]},
    ],
)
```

The catch: the `image` block loads the image from Slack and needs a URL **reachable without authentication**. If the destination requires a session or expires quickly, upload the file with `files.uploadV2` instead of linking it. For general block reference, see the official Block Kit docs: https://docs.slack.dev/block-kit.

## Chaining SocialCutter: the bot that replies with the formats

The full flow:

1. **Someone uploads the image to the channel.** Your service receives the `file_shared` event (or `app_mention`).
2. **The bot downloads the file.** With `url_private_download` and the `xoxb-` token in the `Authorization` header.
3. **The bot calls SocialCutter.** `POST /api/v1/images/process/upload` as multipart, with the binary and the `destinations` list.
4. **SocialCutter replies** with `image_id` and an `outputs` array, one URL per destination.
5. **The bot returns the formats to the channel**, with `files_upload_v2` per output or with an `image` block.

```python
import requests
from slack_sdk import WebClient

SLACK_TOKEN = "xoxb-your-token"
SC_KEY = "sc_your_key"
client = WebClient(token=SLACK_TOKEN)

def process_file(file_id, channel):
    # 1. Metadata and download with the bot token
    info = client.files_info(file=file_id)["file"]
    img = requests.get(info["url_private_download"],
                       headers={"Authorization": f"Bearer {SLACK_TOKEN}"}, timeout=30)
    img.raise_for_status()

    # 2. Ask SocialCutter for the formats
    r = requests.post(
        "https://api.socialcutter.theboomer.dev/api/v1/images/process/upload",
        headers={"X-API-Key": SC_KEY},
        files={"file": ("original.jpg", img.content, "image/jpeg")},
        data={"destinations": '[{"platform":"instagram","format":"post"},'
                              '{"platform":"twitter","format":"post"},'
                              '{"platform":"tiktok","format":"cover"}]'},
        timeout=60,
    )
    r.raise_for_status()
    outputs = r.json()["outputs"]

    # 3. Send each format back to the channel
    for out in outputs:
        f = requests.get(out["url"], timeout=30)
        client.files_upload_v2(
            channel=channel,
            content=f.content,
            filename=f"{out['platform']}-{out['format']}.jpg",
            title=f"{out['platform']} {out['format']} ({out['width']}x{out['height']})",
            initial_comment=f"Ready for {out['platform']}",
        )
```

Honest details about the result: the crop is **centered and deterministic**; there is no subject or face detection. If your image has an off-center subject, check the framing before publishing. And SocialCutter **does not publish** to Instagram, X or TikTok: it hands back files. Slack receives the versions; publishing is another step.

`file_shared` carries the `file_id` and the `channel_id`; the exact payload fields of the event can change between Events API versions, so validate the event against the official Slack docs before depending on a specific field.

## Cost

| Concept | Value |
|---|---|
| Cost per processing | 1 use per destination (platform and format) |
| Duplicate destinations in the same request | Not charged twice |
| Slack file uploads | No API cost, within the workspace limits |
| SocialCutter plans | 0, 3, 9 and 29 EUR, with API and MCP included |

The example bot asks for three destinations: it uses 3 uses per image that lands in the channel and returns three files.

## Common errors

| Error | What is really happening | What to do |
|---|---|---|
| `invalid_auth` | The token is invalid or revoked | Reinstall the app and use the current `xoxb-` |
| `missing_scope` | `files:write` or `chat:write` is missing | Add the scope, reinstall and restart the bot |
| `not_in_channel` | The bot is not in the target channel | Invite it with `/invite` |
| `file_not_found` | The `file_id` does not exist or the bot cannot see it | Check the `files:read` scope and the id |
| The `image` block does not show | The URL is not public or redirects to a login | Upload the file with `files.uploadV2` |
| `413` while processing | The image is over 5 MB | Compress the master or serve a lighter version |
| The file looks blurry in Slack | A small crop was uploaded and is scaled up on screen | Start from a larger master for that destination |

## Next steps

- Python: [Process images with the SocialCutter API from Python](/en/guides/python/)
- Node.js: [Process images with the SocialCutter API from Node.js](/en/guides/node/)
- MCP: [Use SocialCutter from your LLM or editor with MCP](/en/guides/mcp/)
- No-code: [Automate image resizing with Zapier](/en/guides/zapier/) or [n8n](/en/guides/n8n/)
- Strategy: [Automating social media images: the 4 real paths](/en/guides/automatizar-imagenes-redes-sociales/)
- Sizes: [Social media sizes: dimensions and ratios](/en/guides/medidas-redes-sociales/)
- API documentation: https://docs.socialcutter.theboomer.dev