Skip to main content
POST
Upload Attachment
Use this endpoint to turn a local file into a public, workspace-owned URL you can drop straight into attachmentUrls on POST /api/messages or into the GHL Send voice note action. The URL is signed for your workspace, so it always passes the “does not belong to this organization” guard — no need to host the file yourself.

Authentication

Pass your workspace API key as a Bearer token (the same key as /api/messages), or use a Clerk session token.

Request

Send a multipart/form-data request with a single file field. There is no JSON body — this is a file upload.
file
required
The file to upload, as a multipart file field. Max 25 MB. Type is validated by file extension (browsers report unreliable MIME for .caf/.heic).

Allowed types

Voice notes: upload any of the audio formats above. When the file is used with voiceNote: true (GHL action) or otherwise flagged for a voice note, the worker transcodes it to the native iMessage voice-note bubble — you do not have to convert to .caf yourself first.

Example


Response

Returns 200 OK with the public URL and file metadata.
string
A public, signed link hosted on your workspace storage. Use it directly as an attachmentUrl — it needs no auth to fetch and always passes the /api/messages org-ownership guard. Valid for ~1 year; re-upload if a workflow may run longer.
string
The original filename you uploaded.
number
File size in bytes.
string | null
The uploaded file’s MIME type, or null when the browser/client didn’t report one (common for .caf/.heic). Delivery does not depend on this field.
string
The Clerk organization ID that owns the file — the workspace your API key belongs to.
Copy the returned url straight into the GHL Send voice note action’s attachmentUrl field, or into attachmentUrls on POST /api/messages. Because the file lives under your workspace, it passes the ownership guard automatically.

Using the URL

1

Upload the file

POST /api/attachments/upload with your audio/image/etc. as the file field. Grab url from the response.
2

Send it

Pass url as an attachmentUrl — in attachmentUrls on POST /api/messages, or in the GHL Send voice note action.
3

Re-upload if it's older than a year

The signed link is valid for ~1 year. For a long-running workflow, re-upload periodically so the URL never expires mid-campaign.

No-code alternative: the Uploads UI

You don’t need to call the API to get a URL. In the dashboard:
  • /uploads — a dedicated Upload audio etc page.
  • The Upload audio etc tab on the Leads page.
Both give you a drag-and-drop dropzone. Drop a file (or click to choose), then hit Copy to copy the public link — the same URL this endpoint returns. Audio files get a 🎙 Voice-note ready badge and an inline preview player.
This is the fastest path for a one-off voice note: record it, drop it on /uploads, copy the link, and paste it into the GHL Send voice note action’s Attachment URL.

Errors