Media
Files are attached to messages in two steps: upload the file and get its id, then pass the ids in media_ids when you post with POST /api/v1/tickets/{id}/messages. No other endpoint takes attachments. An id must be a file of this project, otherwise you get 422. A file already attached to another message is copied: the copy gets a new id, and the earlier message keeps its file.
/api/v1/media/uploadUpload a file
A multipart request with a file field. Any file type, up to 200 MB (larger gives 413 MEDIA_TOO_LARGE).
mime_typecomes from the Content-Type of thefilepart; without one it’sapplication/octet-streamwidthandheightare filled for images (image/*, except SVG), otherwisenullfile_typeis the file extension in lower case (png,pdf); with no extension it’sbinurlis the relative path for downloading through the API
curl -X POST https://api.support.forestsnet.com/api/v1/media/upload \
-H "Authorization: Bearer sk_xxx" \
-F "file=@/path/to/screenshot.png;type=image/png"{
"id": "m1...",
"url": "/api/v1/media/m1...",
"mime_type": "image/png",
"original_name": "screenshot.png",
"file_type": "png",
"width": 1280,
"height": 720,
"size": 184523
}media = client.media.upload("screenshot.png", content_type="image/png")
print(media.id, media.url)Without content_type the SDK sends application/octet-stream, and the image size isn’t detected.
/api/v1/media/{media_id}Download a file
Serves only files of the key’s project; another project’s id or an unknown id gives 404. If the files are in SupportHub storage, the response is the file itself, with Content-Type from mime_type and Content-Disposition: attachment with the original name. If the file is in the project’s S3 bucket, the response is a 302 to a signed link to the object, valid for an hour, that answers with the same Content-Type and file name; the bucket can be private. Follow it right away (curl -L) rather than storing it.
curl -L -o file.png https://api.support.forestsnet.com/api/v1/media/m1... \
-H "Authorization: Bearer sk_xxx"Attaching to a message
import requests
API = "https://api.support.forestsnet.com/api/v1"
HEAD = {"Authorization": "Bearer sk_xxx"}
# 1. Upload the file
with open("screenshot.png", "rb") as fh:
up = requests.post(
f"{API}/media/upload",
headers=HEAD,
files={"file": ("screenshot.png", fh, "image/png")},
).json()
# 2. Post a message with the attachment (the text is required)
requests.post(
f"{API}/tickets/{ticket_id}/messages",
headers=HEAD,
json={"content": "Here is a screenshot of the error", "media_ids": [up["id"]]},
)A message with an attachment
{
"id": "f1...",
"ticket_id": "8a3f...",
"sender_type": "bot",
"content": "Here is a screenshot of the error",
"is_internal": false,
"media": [
{
"id": "m1...",
"url": "/api/v1/media/m1...",
"mime_type": "image/png",
"original_name": "screenshot.png",
"file_type": "png",
"width": 1280,
"height": 720,
"size": 184523
}
],
"created_at": "2026-04-07T09:00:00",
...
}
