Skip to main content
SocialCutter

CMS and websites

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.

  • 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:

ScopeWhat it is for
filesRead, upload and archive files and folders
files.ui_hidden.readAccess 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:

curl -s -X POST "https://api.hubapi.com/files/v3/files" \
  -H "Authorization: Bearer pat-your_token" \
  -F "[email protected]" \
  -F "folderPath=/socialcutter" \
  -F 'options={"access":"PUBLIC_INDEXABLE"}'
FieldRequiredDescription
fileYesThe binary to upload
folderId or folderPathOne of the twoDestination folder; do not upload to the root
fileNameNoFinal name; generated from the content if omitted
optionsNoJSON 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:

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

accessCan it be inserted into emails, pages and landing pages?
PUBLIC_INDEXABLEYes, and it can also be indexed by search engines
PUBLIC_NOT_INDEXABLEYes, but not indexed
PRIVATENot directly: it needs a signed URL and isUsableInContent is false
SENSITIVENo: 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.

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 placementRecommended sizeRatio
Image inside an email600 px wideVariable (template width rules)
Email header600x2003:1
Blog featured image1200x628~1.91:1
Social share image1200x6301.91:1
Blog thumbnail400x4001:1
Landing hero / full-width1920x1080 (≥1200 px wide)16:9
Site banner2500x6254:1
Author image500x5001: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.
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

ConceptValue
Cost per processing1 use per destination (platform and format)
Duplicate destinations in the same requestNot charged twice
HubSpot File Manager uploadNo extra cost, within the portal limits
SocialCutter plans0, 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

ErrorWhat is really happeningWhat to do
401/403 on uploadThe token lacks the files scopeAdd the scope in the Private app and regenerate the token
Image does not show in the emailIt was uploaded as PRIVATEUpload with public access (PUBLIC_INDEXABLE or PUBLIC_NOT_INDEXABLE)
400 on the multipart uploadMissing folderId/folderPath or the folder does not existCreate the folder first, or use import-from-url, which can create it
429Portal rate limitAdd retries with exponential backoff
Duplicate nameAn identical file already exists in the folderUse duplicateValidationStrategy: RETURN_EXISTING or overwrite
The source URL will not importHubSpot could not download the imageCheck the URL is public and reachable without a session

Next steps

Frequently asked questions

Which scopes does the HubSpot token need?

The files scope to upload, plus files.ui_hidden.read if you also read hidden files. You grant them in a portal Private app, and the token starts with pat-.

Can I upload an image straight from its URL?

Yes. POST /files/v3/files/import-from-url/async downloads the image in the background and returns a task; you poll its status until it says COMPLETE. It is the natural path when SocialCutter already gives you a public URL.

Why does the image not show in a HubSpot email?

Almost always because the file was uploaded with access PRIVATE. A private file is only served with a signed URL and is not valid for emails or pages. Use public access for content.

How is processing billed?

1 use per destination, meaning each platform and format pair. HubSpot does not charge for uploads to the File Manager within the portal limits.

Who decides the crop, HubSpot or SocialCutter?

SocialCutter crops with a centered, deterministic crop before uploading: there is no subject or face detection. HubSpot only stores and serves the file you hand it.