# Upload images to Webflow and use SocialCutter
> Guide to uploading a master image to Webflow Assets with the v2 API, calling SocialCutter and using the correct outputs in a CMS Collection or on page images.
- URL: https://socialcutter.theboomer.dev/en/guides/webflow/
- Idioma: en
- Familia: cms
- Actualizado: 2026-09-24
- Palabras clave: Webflow, API v2, Assets, CMS Collection, SocialCutter, feature image, images
## The problem: one master, many sizes

Webflow lets you upload an image and place it in a CMS Collection or in a page's image field. What it does not do for you is produce that same image at the sizes each network expects: 1200x630 for a Facebook card, 1080x1920 for TikTok, 1200x675 for X. If you upload a single JPG and reuse it everywhere, you end up with forced crops or a cut-off subject.

This guide's flow uploads **one master** to Webflow Assets, processes it with SocialCutter and uses each output where it belongs. One source, every size correct.

## Requirements and site token

You need the **Webflow Data API v2** (base `https://api.webflow.com/v2`) and a site token. Create it under **Site settings → Apps & integrations → API access** and enable the scopes we use:

| Scope | Purpose |
|---|---|
| `assets:read` / `assets:write` | Create and read Assets |
| `cms:read` / `cms:write` | Read and write collection items |
| `sites:read` / `sites:write` | Resolve the `site_id` and publish the site |

The exact scope names are shown on the token creation screen and may vary between versions. The official reference is at https://developers.webflow.com/data/reference. API v2 replaces the old v1: if you find examples with `/sites/{site_id}/assets` without the `/v2` prefix, they belong to the retired version.

Store the token and the `site_id` in variables:

```bash
export WEBFLOW_TOKEN="your_site_token"
export SITE_ID="your_site_id"
export API_URL="https://api.socialcutter.theboomer.dev"
export API_KEY="sc_your_key"
```

## 1. Upload the master to Assets

Uploading in the v2 API takes two steps, exactly as the official *Upload Asset* reference describes: first you create the asset record and the API returns an upload URL with the form details; then you send the file as multipart to that URL.

`POST https://api.webflow.com/v2/sites/{site_id}/assets` · scope `assets:write`

| Field | Required | What it is |
|---|---|---|
| `fileName` | Yes | File name including the extension; under 100 characters |
| `fileHash` | Yes | MD5 hash of the file contents |
| `parentFolder` | No | ID of the Asset folder the file lands in |

### Create the asset

```bash
curl -s -X POST "https://api.webflow.com/v2/sites/$SITE_ID/assets" \
  -H "Authorization: Bearer $WEBFLOW_TOKEN" \
  -H "accept-version: 2.0.0" \
  -H "Content-Type: application/json" \
  -d '{
    "fileName": "master.jpg",
    "fileHash": "md5_hash_of_the_file",
    "parentFolder": "asset_folder_id"
  }' > asset.json

jq '{id, contentType, uploadUrl, assetUrl, hostedUrl, parentFolder}' asset.json
```

The 200 response carries, among others, these fields:

| Field | What it is |
|---|---|
| `id` | Asset identifier; you use it later to read the asset or change its alt text |
| `uploadUrl` | Temporary **presigned Amazon S3 URL** the binary is sent to |
| `uploadDetails` | Metadata for uploading the asset binary: the form fields to send with the file |
| `assetUrl` | S3 link to the asset |
| `hostedUrl` | Link to the asset, the one you reference |
| `parentFolder` | Parent folder for the asset |
| `contentType`, `originalFileName`, `createdOn`, `lastUpdated` | Type, original file name and dates |

The documentation is explicit: you must use `uploadUrl` and `uploadDetails` in the POST request to S3 to complete the upload. That URL is issued by **Webflow**; SocialCutter does not host your file.

The `fileHash` is the MD5 hash of the file contents: generate it with `md5sum master.jpg` (on macOS, `md5 -q master.jpg`). Webflow uses it to **avoid duplicates**: if the hash matches a file that already exists, it does not store it again. If it does not match, the upload fails with `400`. `parentFolder` is the ID of the Assets folder and is optional.

### Create the target folder (optional)

If you do not want assets to land at the site root, create the folder first:

```bash
curl -s -X POST "https://api.webflow.com/v2/sites/$SITE_ID/asset_folders" \
  -H "Authorization: Bearer $WEBFLOW_TOKEN" \
  -H "accept-version: 2.0.0" \
  -H "Content-Type: application/json" \
  -d '{ "displayName": "SocialCutter" }' > folder.json

jq '{id, displayName, parentFolder}' folder.json
```

The endpoint is `POST /v2/sites/{site_id}/asset_folders` (scope `assets:write`) and it accepts `displayName` (required) and `parentFolder` (optional, to nest folders). Keep the folder `id` and pass it as `parentFolder` when creating the asset.

### Send the file

The `uploadDetails` fields must be sent exactly as given, together with the file, to the `uploadUrl`:

```bash
UPLOAD_URL=$(jq -r '.uploadUrl' asset.json)
jq -r '.uploadDetails | to_entries[] | "\(.key)=\(.value)"' asset.json > fields.txt

curl -s -X POST "$UPLOAD_URL" \
  $(while IFS= read -r line; do printf -- "-F %s " "$line"; done < fields.txt) \
  -F "file=@./master.jpg" > upload.json
```

Do not invent the field names: they come from `uploadDetails` and change with the asset type. Send exactly what the API returns.

Size limit: Webflow images must not exceed **4 MB** (documents are capped at 10 MB), per the *Working with Assets* guide. SocialCutter accepts masters up to 5 MB, so a large master may not go straight into Assets: generate it with SocialCutter first, whose outputs are much lighter, or shrink it.

### Verify the asset landed correctly

`GET https://api.webflow.com/v2/assets/{asset_id}` (scope `assets:read`) returns the detail of the uploaded asset:

```bash
curl -s "https://api.webflow.com/v2/assets/$ASSET_ID" \
  -H "Authorization: Bearer $WEBFLOW_TOKEN" \
  -H "accept-version: 2.0.0" \
  | jq '{id, hostedUrl, contentType, size, originalFileName, altText}'
```

The fields that matter for verification: `hostedUrl` (the real link, the one you pass to SocialCutter), `contentType` (file format), `size` (size in bytes), `originalFileName`, `altText` and `variants` (the responsive variants Webflow creates to serve your site responsively). If `hostedUrl` does not load when opened, the upload to `uploadUrl` did not complete: repeat the POST with the `uploadDetails` fields and the file.

The same detail can be listed per folder. Note that `folderId` only appears in list responses, not when querying a single asset. The list endpoint takes `folderId` (a 24-character hex ObjectId) and pagination with `limit` (max 100) and `offset`:

```bash
curl -s "https://api.webflow.com/v2/sites/$SITE_ID/assets?folderId=$FOLDER_ID&limit=100" \
  -H "Authorization: Bearer $WEBFLOW_TOKEN" \
  -H "accept-version: 2.0.0" \
  | jq '.assets[] | {id, hostedUrl, size, folderId, altText}'
```

Alt text and the display name are changed with `PATCH https://api.webflow.com/v2/assets/{asset_id}` (scope `assets:write`), sending `altText` and/or `displayName`.

## 2. Call SocialCutter with the asset URL

Once the master is in Assets, its `hostedUrl` is the source for SocialCutter:

```bash
curl -s -X POST "$API_URL/api/v1/images/process" \
  -H "X-API-Key: *** \
  -H "Content-Type: application/json" \
  -d "{
    \"source\": { \"type\": \"url\", \"value\": \"$(jq -r '.hostedUrl' asset.json)\" },
    \"destinations\": [
      { \"platform\": \"facebook\", \"format\": \"link\" },
      { \"platform\": \"instagram\", \"format\": \"post\" },
      { \"platform\": \"twitter\", \"format\": \"summary_large_image\" }
    ]
  }" > sc.json

jq '.image_id, (.outputs[] | {url, platform, format, width, height})' sc.json
```

Each element of `outputs` carries the output URL, its platform, its format and its dimensions. The `cover` crop (the default) is centered.

## 3. Publish the site

New Assets and created items do not show on the published site until you publish it:

```bash
curl -s -X POST "https://api.webflow.com/v2/sites/$SITE_ID/publish" \
  -H "Authorization: Bearer $WEBFLOW_TOKEN" \
  -H "accept-version: 2.0.0" \
  -H "Content-Type: application/json" \
  -d '{ "publishToWebflowSubdomain": true, "customDomains": ["your-domain.com"] }'
```

Adjust `customDomains` to the project's real domains.

## 4. Use the outputs in the CMS Collection

If the CMS Collection has an image field, there are two routes: upload each output as an Asset (repeating step 1) and reference its `id`, or pass the URL directly if your field accepts it. Check the collection schema before building the item:

```bash
# List collections
curl -s "https://api.webflow.com/v2/sites/$SITE_ID/collections" \
  -H "Authorization: Bearer $WEBFLOW_TOKEN" \
  -H "accept-version: 2.0.0" | jq '.collections[] | {id, slug}'

# Schema of one collection (fields and types)
curl -s "https://api.webflow.com/v2/collections/$COLLECTION_ID" \
  -H "Authorization: Bearer $WEBFLOW_TOKEN" \
  -H "accept-version: 2.0.0" | jq '.fields[] | {slug, type}'
```

Create the live item with the image field value taken from `outputs[0].url`:

```bash
curl -s -X POST "https://api.webflow.com/v2/collections/$COLLECTION_ID/items/live" \
  -H "Authorization: Bearer $WEBFLOW_TOKEN" \
  -H "accept-version: 2.0.0" \
  -H "Content-Type: application/json" \
  -d "{
    \"fieldData\": {
      \"name\": \"Sample post\",
      \"slug\": \"sample-post\",
      \"image\": \"$(jq -r '.outputs[0].url' sc.json)\"
    }
  }"
```

The real name of the image field is the `slug` the schema returns; it is not necessarily called `image`.

## 5. Use the outputs as page images

For a standalone image on a page, upload the output to Assets and use its URL in the HTML. In practice the cleanest pattern is: upload the master, process with SocialCutter and upload each output once, keeping its `hostedUrl` to reference from the CMS or from pages.

That order is the point: the file you upload to Assets is already produced by SocialCutter **at the destination's proportion** (1080x1080 for `instagram` `post`, 1200x627 for `linkedin` `post`), as a centred crop. Webflow only hosts it and creates its responsive variants, so the CDN serves files that are already at the correct size instead of re-cropped versions of the original master.

## Cost

- **1 use per destination** (platform and format combination) per request.
- Repeated destinations in the same request are not charged twice.
- Failed processing runs are refunded.

## Common errors

| Situation | Likely cause |
|---|---|
| 401 from Webflow | Token missing, malformed or lacking the required scope (`assets:write` to create the asset) |
| 400 creating the asset | `fileHash` does not match the file's MD5, or `fileName` is over 100 characters |
| Asset stays empty or `hostedUrl` does not load | The POST to `uploadUrl` was not completed with the `uploadDetails` fields |
| Image not showing | Site not published or item still a draft |
| 401 from SocialCutter | `sc_` key malformed or revoked |
| 413 from SocialCutter | Master file exceeds 5 MB |
| Master will not upload to Webflow | It exceeds Webflow's 4 MB per-image limit |
| 429 from SocialCutter | Wallet quota exhausted |
| Empty image field | The field `slug` is not what you assumed: check the schema |
| An asset is duplicated or missing | Webflow uses the `fileHash` to avoid storing files with the same MD5 twice |

## Next steps

- WordPress guide: [Publish the correct sizes in WordPress](/en/guides/wordpress/)
- Shopify guide: [Product and blog images in Shopify](/en/guides/shopify/)
- Automation: [Orchestrate the flow with n8n](/en/guides/n8n/)
- API from the terminal: [Process images with curl](/en/guides/curl/)
- Official Webflow reference: https://developers.webflow.com/data/reference