Media uploads and library
Send images, audio, video, documents, and stickers without hosting public URLs. Tyxter stores media by lifecycle and gives providers signed fetch URLs at send time.
Media assets are scoped to one environment and count toward a hard 500 MB active-media quota by default. Use
single_use for one-off sends and library for reusable assets. Library assets are retained until deleted.Choose a lifecycle
single_useis the default. The asset can be attached to one message, then remains fetchable for that owning message for 24 hours before cleanup.libraryassets can be reused across messages and are the only supported media lifecycle for WhatsApp template header media in broadcast batches.
Upload library media once, send many times
- Create an upload session with
POST /v1/media/uploads. - PUT the file bytes directly to the returned storage URL.
- Complete the upload with
POST /v1/media/uploads/{asset_id}/complete. - Send messages with
message.media.asset_id.
GET/v1/media
POST/v1/media/uploads
POST/v1/media/uploads/{'{asset_id}'}/complete
GET/v1/media/storage-usage
GET/v1/media/{'{asset_id}'}
DELETE/v1/media/{'{asset_id}'}
Python broadcast script
This example uploads one image, completes it, then reuses the returnedasset_id across a list of recipients. The script sends individual messages so every recipient can have its own idempotency key.
import mimetypes
import pathlib
import requests
API_BASE = "https://api.tyxter.com"
API_KEY = "tx_live_..."
FROM_PHONE_NUMBER_ID = "pn_..."
RECIPIENTS = ["+5511999999001", "+5511999999002"]
FILE_PATH = pathlib.Path("promo.png")
headers = {
"authorization": f"Bearer {API_KEY}",
"content-type": "application/json",
}
data = FILE_PATH.read_bytes()
mime_type = mimetypes.guess_type(FILE_PATH.name)[0] or "application/octet-stream"
upload = requests.post(
f"{API_BASE}/v1/media/uploads",
headers={**headers, "idempotency-key": "media-upload-promo-v1"},
json={
"kind": "image",
"lifecycle": "library",
"filename": FILE_PATH.name,
"mime_type": mime_type,
"byte_length": len(data),
},
timeout=30,
)
upload.raise_for_status()
session = upload.json()
put = requests.put(
session["upload_url"],
headers=session["upload_headers"],
data=data,
timeout=120,
)
put.raise_for_status()
complete = requests.post(
f"{API_BASE}/v1/media/uploads/{session['id']}/complete",
headers={**headers, "idempotency-key": "media-complete-promo-v1"},
timeout=30,
)
complete.raise_for_status()
asset_id = complete.json()["id"]
for phone in RECIPIENTS:
response = requests.post(
f"{API_BASE}/v1/messages",
headers={**headers, "idempotency-key": f"promo-image-{phone}"},
json={
"channel": "whatsapp",
"sender": {"type": "whatsapp_phone_number", "id": FROM_PHONE_NUMBER_ID},
"recipient": {"type": "phone_e164", "id": phone},
"message": {
"type": "media",
"media": {
"kind": "image",
"asset_id": asset_id,
"caption": "Launch promo",
},
},
},
timeout=30,
)
response.raise_for_status()
print(phone, response.json()["id"])Inline convenience
For small single sends, message.media.inline can carry base64 bytes directly in the message request. Tyxter immediately stores those bytes as a single_use asset and persists only the generated asset_id. Inline media is limited to 2 MB and should not be used in bulk loops.
Limits
- Default environment storage quota: 500 MB across active media.
- Default single-use retention before send: 7 days from upload creation.
- Consumed single-use retention: 24 hours for the owning message.
- Library retention: until deleted.
- Upload session expiry: 30 minutes.
- Inline media raw-size limit: 2 MB.
- Image: 5 MB.
- Audio: 16 MB.
- Video: 16 MB.
- Sticker: 500 KB and WebP only.
- Document: 100 MB.