Skip to main content
POST
Upload an attachment file
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, so Content-Type: application/json is rejected with 400 Expected multipart/form-data with a "file" field. The file is capped at 5 MB and the type is validated by file extension (browsers report unreliable MIME for .caf and .heic).
5 MB is the upload limit, not iMessage’s. A message attachment supplied as a public URL (attachmentUrls on send-message) can still be up to 25 MB — that file never passes through Tuco. Uploads are held lower because they stream through our servers, and because a voice note is capped at 5 minutes of audio anyway.

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-sequence.

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

Authorizations

Authorization
string
header
required

Workspace API key: Authorization: Bearer tuco_xxxxxxxxxxxxx

Body

multipart/form-data
file
file
required

The file to upload. Max 5 MB. Type validated by extension.

Response

Uploaded — returns the public URL and file metadata

url
string
required

Public, signed link (valid ~1 year) hosted on your workspace storage. Use directly as an attachmentUrl.

name
string
required

Original filename.

size
integer
required

File size in bytes.

workspaceId
string
required

Clerk organization ID that owns the file.

contentType
string | null

MIME type, or null when the client didn't report one.