Media Quick Start
This guide gets you uploading images to Mushu Media in under 5 minutes. You'll learn both CLI and API approaches.
First time? Complete the Quick Start to set up CLI, organization, and API keys.
Option 1: CLI Upload
The fastest way to upload an image:
mushu media upload photo.jpg --org ORG_ID The CLI will:
- Request an upload URL from Mushu
- Upload your file directly to Cloudflare R2
- Confirm the upload and trigger processing
- Return the image URL and variants
Output:
{
"media_id": "media_abc123",
"status": "ready",
"url": "https://images.mushucorp.com/t/original/org_xxx/media_abc123/photo.jpg",
"variants": {
"thumbnail": "https://images.mushucorp.com/t/thumbnail/...",
"small": "https://images.mushucorp.com/t/small/...",
"medium": "https://images.mushucorp.com/t/medium/...",
"large": "https://images.mushucorp.com/t/large/..."
}
} Option 2: curl Upload
For API integration, here's the 4-step flow using curl.
Step 1: Get an Upload URL
With an API key (server-to-server):
curl -X POST https://media.mushucorp.com/media/upload-url \
-H "X-API-Key: msk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"org_id": "org_xxx",
"filename": "photo.jpg",
"content_type": "image/jpeg",
"size_bytes": 102400
}' Or with a user session token:
curl -X POST https://media.mushucorp.com/media/upload-url \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"org_id": "org_xxx",
"filename": "photo.jpg",
"content_type": "image/jpeg",
"size_bytes": 102400
}' Response:
{
"media_id": "media_abc123",
"upload_url": "https://xxx.r2.cloudflarestorage.com/...",
"key": "org_xxx/media_abc123/photo.jpg",
"expires_in": 3600
} Step 2: Upload the File
Use the upload_url from the previous response:
curl -X PUT "UPLOAD_URL_FROM_STEP_1" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg Note: This request goes directly to Cloudflare R2, not to the Mushu API. No authorization header is needed—the presigned URL includes the credentials.
Step 3: Confirm the Upload
Tell Mushu the upload is complete so it can process the image:
curl -X POST https://media.mushucorp.com/media/media_abc123/confirm \
-H "X-API-Key: msk_live_YOUR_KEY" Response:
{
"status": "ready",
"url": "https://images.mushucorp.com/t/original/org_xxx/media_abc123/photo.jpg"
} Step 4: Get Image Variants
Retrieve all available image sizes:
curl https://media.mushucorp.com/media/media_abc123/images \
-H "X-API-Key: msk_live_YOUR_KEY" Response:
{
"original": "https://images.mushucorp.com/t/original/...",
"thumbnail": "https://images.mushucorp.com/t/thumbnail/...",
"small": "https://images.mushucorp.com/t/small/...",
"medium": "https://images.mushucorp.com/t/medium/...",
"large": "https://images.mushucorp.com/t/large/..."
} Python SDK
Install the SDK:
pip install mushu Upload using the SDK:
import asyncio
import os
import httpx
import mushu
from mushu.media.api.media import get_upload_url, confirm_upload, get_image_urls
from mushu.media.models import UploadUrlRequest
# Create client with API key
media = mushu.client("media", api_key=os.environ["MUSHU_API_KEY"])
async def upload_image(file_path: str, org_id: str) -> dict:
"""Upload an image and return the media URLs."""
filename = os.path.basename(file_path)
file_size = os.path.getsize(file_path)
# Determine content type
ext = filename.lower().split(".")[-1]
content_types = {"jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png"}
content_type = content_types.get(ext, "application/octet-stream")
# Step 1: Get upload URL
upload_data = await get_upload_url.asyncio(
client=media,
body=UploadUrlRequest(
org_id=org_id,
filename=filename,
content_type=content_type,
size_bytes=file_size,
),
)
# Step 2: Upload to presigned URL (direct to R2)
async with httpx.AsyncClient() as http:
with open(file_path, "rb") as f:
await http.put(
upload_data.upload_url,
headers={"Content-Type": content_type},
content=f.read(),
)
# Step 3: Confirm upload
await confirm_upload.asyncio(client=media, media_id=upload_data.media_id)
# Step 4: Get variants
images = await get_image_urls.asyncio(client=media, media_id=upload_data.media_id)
return {"media_id": upload_data.media_id, "images": images}
async def main():
result = await upload_image("photo.jpg", "org_xxx")
print(f"Media ID: {result['media_id']}")
print(f"Images: {result['images']}")
asyncio.run(main()) Image Variants
Mushu automatically generates these variants for each uploaded image:
| Variant | Max Dimension | Use Case |
|---|---|---|
thumbnail | 150px | Lists, grids, previews |
small | 320px | Mobile feeds |
medium | 640px | Detail views, cards |
large | 1280px | Full-screen mobile, web |
original | Original size | Downloads, high-res |
Supported Formats
- JPEG, PNG, GIF, WebP
- Maximum file size: 10MB
Next Steps
- Uploading Media - Full API reference
- Working with Images - Transforms and CDN
- Working with Videos - Video transcoding
- API Keys - Server-to-server authentication
Troubleshooting
403 Forbidden on upload
The presigned URL has expired. URLs are valid for 1 hour. Request a new upload URL and try again.
Image not processing
Make sure you called the confirm endpoint after uploading. Without confirmation, Mushu doesn't know the upload is complete.
Unsupported content type
Check that you're uploading a supported format (JPEG, PNG, GIF, WebP) and
that the content_type in your request matches the actual file.