DEV Community

Kayabyte
Kayabyte

Posted on Originally published at kayabyte.dev AI-assisted

HubSpot API: how to get attachments from notes and emails (hs_attachment_ids)

Attachments in HubSpot are not a property of the contact. They hang off engagements (notes, emails, calls, meetings, tasks) that are associated with the record. This is the chain of calls, the scopes, and why Files API calls return 404 or 429. Endpoint paths and findings below are what we used and saw while building an exporter on 11-12 November 2026 (API date version 2026-09); check HubSpot's reference for your version.

The chain

  1. Find the records. Read contacts, companies, deals or tickets with the CRM objects API (/crm/objects/2026-09/{objectType}, search or batch read), or the members of a list (/crm/lists/2026-09/{listId}/memberships).
  2. Find their engagements. Use the associations batch read (POST /crm/associations/2026-09/{fromType}/{toType}/batch/read) from the record type to notes, emails, calls, meetings and tasks. Page through results when a record has many.
  3. Read the attachment IDs. Batch-read the engagements with the properties hs_attachment_ids and hs_timestamp. hs_attachment_ids is a semicolon-separated string of file IDs, or empty. We saw the property on all five engagement types.
  4. Read the file. GET /files/2026-09/files/{fileId} returns the name, extension, size and access level. HubSpot's type field is a category such as DOCUMENT or IMG, not a MIME type, so derive the MIME type from the extension. The Files search call (GET /files/2026-09/files/search?ids=...) returns metadata for up to 100 files at once, and files that do not come back are not visible to your token.
  5. Download it. Public files can be fetched from their URL. Private files return 404 there; request a signed URL with GET /files/2026-09/files/{fileId}/signed-url and download from that.

Scopes

In our build the working set for read-only access was: files.read, files.ui_hidden.read, crm.objects.contacts.read (also enough for notes, calls, meetings and tasks in our tests), crm.objects.companies.read, crm.objects.deals.read, crm.objects.tickets.read, crm.lists.read and sales-email-read for emails. The older scope names files and tickets are now files.read and crm.objects.tickets.read: an upload with the old names failed. Files marked hidden (an attachment added through the UI is HIDDEN_PRIVATE) are covered by files.ui_hidden.read. In our test a note attachment was readable with files.read alone, but we ask for the hidden-files scope anyway so no hidden file type is missed.

Why you get 404

  • The file was deleted (HubSpot may mark it archived).
  • The file is private and you used its normal URL instead of a signed URL.
  • The file is hidden and your token may lack files.ui_hidden.read. In our test a note attachment (access level HIDDEN_PRIVATE) was readable with files.read alone, so this was not the cause there; we did not test other hidden file types such as email attachments, which is why we request the scope anyway.

Treat a 404 as "missing" only after you have checked the scopes.

Sensitive files

Files with a sensitive access level cannot be fetched with a signed URL ("Cannot access sensitive file"). HubSpot's download endpoint for them needs a separate permission that staff describe as for privately distributed apps. Whether a Marketplace app can request it is something we did not confirm, so our app does not.

Rate limits

HubSpot's documentation gives Marketplace OAuth apps 110 requests per 10 seconds per installing account (CRM search has its own limit). Read the X-HubSpot-RateLimit headers and wait for Retry-After on a 429. A large export is many calls: metadata for each file and one signed URL per private file.

Other traps we met

  • HubSpot renames same-named uploads, so two files can have names that look unrelated.
  • Names can contain characters that are illegal on Windows or macOS paths; clean them before writing to disk.
  • The Files API import-from-URL refused to create hidden files in our test account, so test data for hidden attachments has to be made in the HubSpot UI.

When to use an app instead

If you need this once or twice, a script is fine. If you need a manifest, restart after a failure and many thousands of files, you are building an exporter. Full disclosure: we built Attachment Exporter: Bulk File Download, which does this chain, streams files into zips and writes the manifest. Free to 250 files, then packs from USD 49; it installs from our site, not the HubSpot Marketplace.

Original with FAQ: https://kayabyte.dev/hubspot/guides/hubspot-api-attachments-hs-attachment-ids

AI-assisted: written with AI help for grammar and layout, facts checked by a person. HubSpot is a registered trademark of HubSpot, Inc.; Kayabyte is an independent developer and not affiliated with HubSpot.

Top comments (0)