# Upload images to HubSpot with the Files API
> Send the images SocialCutter produces to the HubSpot File Manager with the Files API, by multipart or from a URL, and use them in emails, pages and social.
- URL: https://socialcutter.theboomer.dev/en/guides/hubspot/
- Idioma: en
- Familia: cms
- Actualizado: 2026-09-24
- Palabras clave: HubSpot, Files API, File Manager, import-from-url, scopes, SocialCutter, email marketing
## Why go through the File Manager

HubSpot's **File Manager** is the portal's media library: everything you insert into emails, pages, landing pages and blog posts is served from there, through HubSpot's CDN. SocialCutter generates centered-crop versions at the exact dimensions of each destination and returns a public URL per output; if those images will live inside HubSpot, it is better to upload them to the File Manager than to link external URLs: you manage them, reuse them and index them with the rest of your assets.

The boundary matters: **SocialCutter generates images, it does not publish.** HubSpot will not publish to Instagram or LinkedIn for you from the File Manager either; it only stores and serves the file.

## Requirements: Private app and scopes

Create a **Private app** under Settings → Integrations → Private apps and tick the Files API scopes:

| Scope | What it is for |
|---|---|
| `files` | Read, upload and archive files and folders |
| `files.ui_hidden.read` | Access hidden files that do not show in the public listing |

The token starts with `pat-` and travels in the `Authorization: Bearer pat-…` header. The complete Files API reference, with the current scopes and fields, lives at https://developers.hubspot.com/docs/api-reference/latest/files/guide.

## Upload a file by multipart

`POST /files/v3/files` accepts `multipart/form-data`. One file per request:

```bash
curl -s -X POST "https://api.hubapi.com/files/v3/files" \
  -H "Authorization: Bearer pat-your_token" \
  -F "file=@instagram-post.jpg" \
  -F "folderPath=/socialcutter" \
  -F 'options={"access":"PUBLIC_INDEXABLE"}'
```

| Field | Required | Description |
|---|---|---|
| `file` | Yes | The binary to upload |
| `folderId` or `folderPath` | One of the two | Destination folder; do not upload to the root |
| `fileName` | No | Final name; generated from the content if omitted |
| `options` | No | JSON with `access` and, optionally, `ttl` (1 day to 1 year) |

The `201` response carries `id`, `path`, `url`, `defaultHostingUrl`, `access`, `width`, `height` and `isUsableInContent`. Keep the `id` and the `url`: they are what you use later in the email editor or the image picker.

## Upload from a URL with import-from-url

When SocialCutter already returns URLs, you do not need to download and re-upload by hand. HubSpot imports from a URL asynchronously:

```bash
curl -s -X POST "https://api.hubapi.com/files/v3/files/import-from-url/async" \
  -H "Authorization: Bearer pat-your_token" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://cdn.socialcutter.theboomer.dev/out/twitter-post.jpg",
    "access": "PUBLIC_INDEXABLE",
    "folderPath": "/socialcutter",
    "duplicateValidationStrategy": "REJECT",
    "duplicateValidationScope": "EXACT_FOLDER"
  }'
```

The `202` response returns a task `id`. You poll the status with `GET /files/v3/files/import-from-url/async/tasks/{taskId}/status`, which answers `PENDING`, `PROCESSING`, `COMPLETE` or `CANCELED`. With `COMPLETE` the file is already in the File Manager.

Version note: HubSpot has been moving the Files API to versioned routes (from `files/v3` to schemes such as `files/2026-09`), and scope names have changed between versions. Before hard-coding routes in your code, confirm the current route and scopes in the official documentation linked above.

## Access levels and where each file can be used

| `access` | Can it be inserted into emails, pages and landing pages? |
|---|---|
| `PUBLIC_INDEXABLE` | Yes, and it can also be indexed by search engines |
| `PUBLIC_NOT_INDEXABLE` | Yes, but not indexed |
| `PRIVATE` | Not directly: it needs a signed URL and `isUsableInContent` is `false` |
| `SENSITIVE` | No: meant for data, not for content |

If the image is going into an email or a landing page, use public access. A private file will not render in the email because the mail client cannot sign the URL.

## HubSpot recommended sizes

These are the sizes HubSpot publishes for the places images are used inside the portal. For the exact dimensions that SocialCutter **generates** per social network, the reference is the internal measures guide (linked below).

| HubSpot placement | Recommended size | Ratio |
|---|---|---|
| Image inside an email | 600 px wide | Variable (template width rules) |
| Email header | 600x200 | 3:1 |
| Blog featured image | 1200x628 | ~1.91:1 |
| Social share image | 1200x630 | 1.91:1 |
| Blog thumbnail | 400x400 | 1:1 |
| Landing hero / full-width | 1920x1080 (≥1200 px wide) | 16:9 |
| Site banner | 2500x625 | 4:1 |
| Author image | 500x500 | 1:1 |

HubSpot sources: its social media sizes guide (https://blog.hubspot.com/marketing/ultimate-guide-social-media-image-dimensions-infographic) and its website image sizes guide (https://blog.hubspot.com/website/image-size-for-website). Watch the fine differences: HubSpot's social share is 1200x630 and its blog featured image 1200x628; the closest SocialCutter destination is `facebook` + `post` (1200x630). If you need a size the catalogue does not produce, crop it separately.

## Flow: from SocialCutter to the File Manager

1. Process the master: `POST /api/v1/images/process` with `source` and `destinations`.
2. Walk the `outputs` array and fire an `import-from-url/async` per URL.
3. Wait for `COMPLETE` per task and collect the final `url`.
4. Insert that URL into the email, landing page or blog post.

```python
import time, requests

HUB = {"Authorization": "Bearer pat-your_token"}
BASE = "https://api.hubapi.com/files/v3/files/import-from-url/async"

def upload(url, folder="/socialcutter"):
    r = requests.post(BASE, headers={**HUB, "Content-Type": "application/json"},
                      json={"url": url, "access": "PUBLIC_INDEXABLE", "folderPath": folder},
                      timeout=30)
    r.raise_for_status()
    task = r.json()["id"]
    while True:
        s = requests.get(f"{BASE}/tasks/{task}/status", headers=HUB, timeout=30).json()
        if s.get("status") in ("COMPLETE", "CANCELED"):
            return s
        time.sleep(2)

for out in outputs:            # outputs comes from SocialCutter
    print(out["platform"], upload(out["url"]))
```

## Cost

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

A request for Instagram post + LinkedIn post + X post uses 3 SocialCutter uses and produces 3 HubSpot uploads.

## Common errors

| Error | What is really happening | What to do |
|---|---|---|
| `401`/`403` on upload | The token lacks the `files` scope | Add the scope in the Private app and regenerate the token |
| Image does not show in the email | It was uploaded as `PRIVATE` | Upload with public access (`PUBLIC_INDEXABLE` or `PUBLIC_NOT_INDEXABLE`) |
| `400` on the multipart upload | Missing `folderId`/`folderPath` or the folder does not exist | Create the folder first, or use `import-from-url`, which can create it |
| `429` | Portal rate limit | Add retries with exponential backoff |
| Duplicate name | An identical file already exists in the folder | Use `duplicateValidationStrategy: RETURN_EXISTING` or `overwrite` |
| The source URL will not import | HubSpot could not download the image | Check the URL is public and reachable without a session |

## Next steps

- API guide with curl: [Process images with the API from the terminal](/en/guides/curl/)
- Python: [Process images with the SocialCutter API from Python](/en/guides/python/)
- No-code: [Automate image resizing with Zapier](/en/guides/zapier/), [Make](/en/guides/make/) 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