Aauti Media Platform - Integration Guide
Everything you need to upload files, stream video, and deliver media from your application.
Quick Start
1. Upload a file
curl -X POST https://upload.example.com/upload/direct-upload \
-H "API-Key: your_tenant_api_key" \
-F "file=@video.mp4" \
-F "userId=user123" \
-F "destination=lectures"
2. Get a signed playback URL
curl -X POST https://api.example.com/auth/url-token \
-H "API-Key: your_tenant_api_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://cdn.example.com/fileId/path/video.mp4", "expiresIn": "4h"}'
3. Play the file
https://delivery.example.com/<signed_token>/path/video.mp4
Authentication
API Key (Tenant Authentication)
All upload and API calls require a tenant API key.
| Header | Value |
|---|---|
API-Key |
Your tenant's app_token |
curl -H "API-Key: tk_abc123def456" https://api.example.com/files/duration?file=...
Signed URL Tokens (File Access)
Protected files require a JWT token in the URL path. Generate tokens via the API:
POST /auth/url-token
The token replaces the first path segment in the delivery URL:
https://delivery.example.com/<jwt_token>/tenant/user/file.mp4
Environment URLs
| Service | Development | Test | QA | Demo | Production |
|---|---|---|---|---|---|
| API | media-apidev.aauti.com |
media-apitest.aauti.com |
media-apiqa.aauti.com |
media-apidemo.aauti.com |
media-api.aauti.com |
| Upload | upload-dev.aauti.com |
upload-test.aauti.com |
upload-qa.aauti.com |
upload-demo.aauti.com |
upload.aauti.com |
| Delivery | media-dev.aauti.com |
media-test.aauti.com |
media-qa.aauti.com |
media-demo.aauti.com |
media.aauti.com |
| Dashboard | media-dashboarddev.aauti.com |
media-dashboardtest.aauti.com |
media-dashboardqa.aauti.com |
media-dashboarddemo.aauti.com |
media-dashboard.aauti.com |
In all examples,
{API_BASE}= API Service URL,{UPLOAD_BASE}= Upload Service URL, and{DELIVERY_BASE}= Delivery Service URL for your environment.
Upload Service API
Direct Upload
Upload a file in a single request using multipart/form-data.
POST /upload/direct-upload
Headers:
| Header | Required | Description |
|---|---|---|
API-Key |
Yes | Tenant API key |
Form Fields:
| Field | Required | Description |
|---|---|---|
file |
Yes | The file binary |
bucket |
No | Target bucket name (uses default if omitted) |
userId |
No | Uploader ID (default: anonymous) |
destination |
No | Subfolder path (e.g., course/materials) |
requireSignedUrl |
No | true to require signed URLs for access (default: false) |
Response 201 Created:
{
"key": "tenant_id/user_id/destination/filename.mp4",
"url": "https://cdn.example.com/file_id/tenant_id/user_id/destination/filename.mp4",
"duration": 120.5
}
Duplicate handling: If a completed upload with the same filename already exists, a sequential suffix is appended:
video.mp4->video_1.mp4->video_2.mp4.
Errors:
| Status | Reason |
|---|---|
| 400 | Missing API-Key or file |
| 404 | Invalid API key or bucket not found |
URL Upload (Async)
Upload a file from a public URL. Returns immediately; processing happens in background.
POST /upload/from-url
Headers:
| Header | Required | Description |
|---|---|---|
API-Key |
Yes | Tenant API key |
Content-Type |
Yes | application/json |
Body:
{
"url": "https://example.com/video.mp4",
"bucket": "my-bucket",
"userId": "user123",
"destination": "course/materials",
"filename": "custom-name.mp4",
"requireSignedUrl": false,
"meta": {
"recordingId": "rec_abc123",
"name": "Lecture 1"
}
}
| Field | Required | Description |
|---|---|---|
url |
Yes | Public URL to download from |
bucket |
No | Target bucket |
userId |
No | Uploader ID |
destination |
No | Subfolder path |
filename |
No | Override filename |
requireSignedUrl |
No | Require signed access |
meta |
No | Custom metadata (passed through to webhooks) |
Response 202 Accepted:
{
"jobId": "unique-job-id",
"status": "queued",
"message": "Upload from URL queued for processing. Webhook will be triggered on completion."
}
cURL Example:
curl -X POST https://upload.aauti.com/upload/from-url \
-H "API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/lecture-video.mp4",
"userId": "user-123",
"destination": "courses/math",
"filename": "lecture-1.mp4",
"meta": { "recordingId": "64f1a2b3c4d5e6f7a8b9c0d1", "courseName": "Math 101" }
}'
Node.js Example:
const response = await fetch('https://upload.aauti.com/upload/from-url', {
method: 'POST',
headers: { 'API-Key': 'your-api-key', 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'https://example.com/lecture-video.mp4', userId: 'user-123',
destination: 'courses/math', meta: { recordingId: '64f1a2b3c4d5e6f7a8b9c0d1' },
}),
});
const { jobId } = await response.json(); // now wait for webhook
Webhook Payloads
Success:
{
"eventType": "file",
"data": {
"key": "tenantId/user-123/courses/math/lecture-1.mp4",
"fileUrl": "https://media.aauti.com/fileId/tenantId/user-123/courses/math/lecture-1.mp4",
"filename": "lecture-1.mp4",
"file_type": "video/mp4",
"file_size": 5242880,
"user_id": "user-123",
"source_url": "https://example.com/lecture-video.mp4",
"meta": { "recordingId": "64f1a2b3c4d5e6f7a8b9c0d1", "courseName": "Math 101" },
"duration": 120.5
}
}
durationonly included for video files.metaonly included if provided in the original request.
Failure:
{
"eventType": "file",
"data": {
"source_url": "https://example.com/lecture-video.mp4",
"user_id": "user-123",
"status": "failed",
"errorMessage": "URL returned HTTP 403 Forbidden",
"meta": { "recordingId": "64f1a2b3c4d5e6f7a8b9c0d1" }
}
}
Retry Behavior
| Failure Type | Retries | Examples |
|---|---|---|
| 4xx from source URL | None | 403 Forbidden, 404 Not Found |
| Private/internal URL | None | Redirect to 192.168.x.x |
| File too large / invalid tenant | None | Exceeds max size, revoked API key |
| 5xx from source URL | 3 | 502 Bad Gateway, 503 |
| Network timeout / storage failure | 3 | DNS failure, temporary S3/R2 error |
Retries use exponential backoff: 5s → 10s → 20s
Limits
| Setting | Value |
|---|---|
| Max file size | 20 GB (configurable per tenant) |
| Download timeout | 5 minutes overall, 30s connection |
| Max URL redirects | 5 hops |
| URL max length | 2048 characters |
| Webhook delivery | 5 attempts, exponential backoff |
Errors:
| Status | Reason |
|---|---|
| 400 | Missing API-Key, invalid URL, or URL not accessible |
TUS Resumable Upload
For large files, use the TUS protocol for resumable, chunked uploads.
Base path: /files
Required headers for all TUS requests:
| Header | Value |
|---|---|
API-Key |
Tenant API key |
Tus-Resumable |
1.0.0 |
Upload metadata (base64-encoded in Upload-Metadata header):
| Key | Description |
|---|---|
filename |
Original filename |
filetype |
MIME type |
userId |
Uploader ID |
destination |
Subfolder path |
requireSignedUrl |
true / false |
TUS lifecycle:
1. POST /files -- Create upload, get Location header
2. PATCH /files/:upload_id -- Send chunks (repeat until complete)
3. HEAD /files/:upload_id -- Check progress (Upload-Offset)
4. DELETE /files/:upload_id -- Cancel upload
Example - Create upload:
curl -X POST https://upload.example.com/files \
-H "API-Key: your_api_key" \
-H "Tus-Resumable: 1.0.0" \
-H "Upload-Length: 104857600" \
-H "Upload-Metadata: filename dmlkZW8ubXA0,filetype dmlkZW8vbXA0,userId dXNlcjEyMw=="
Example - Upload chunk:
curl -X PATCH https://upload.example.com/files/abc123 \
-H "API-Key: your_api_key" \
-H "Tus-Resumable: 1.0.0" \
-H "Upload-Offset: 0" \
-H "Content-Type: application/offset+octet-stream" \
--data-binary @chunk1.bin
Check upload status:
HEAD /files/:uploadId
Response headers:
Upload-Offset: 5242880
Upload-Length: 104857600
Tus-Resumable: 1.0.0
Cancel upload:
DELETE /files/:uploadId
Returns 204 — upload cancelled and cleaned up.
TUS JavaScript Client Example:
import * as tus from "tus-js-client";
const file = document.getElementById("fileInput").files[0];
const upload = new tus.Upload(file, {
endpoint: "https://upload.aauti.com/files",
headers: { "API-Key": "your-api-key", "Bucket": "my-bucket" },
metadata: { filename: file.name, filetype: file.type, userId: "user123", destination: "course/materials" },
chunkSize: 5 * 1024 * 1024,
onProgress: (bytesUploaded, bytesTotal) => {
console.log(`${((bytesUploaded / bytesTotal) * 100).toFixed(2)}%`);
},
onSuccess: () => console.log("Upload complete:", upload.url),
onError: (error) => console.error("Upload failed:", error),
});
upload.start();
API Service
Generate Signed URL Token
POST /auth/url-token
Headers:
| Header | Required | Description |
|---|---|---|
API-Key |
Yes | Tenant API key |
Content-Type |
Yes | application/json |
Body:
{
"url": "https://cdn.example.com/file_id/path/video.mp4",
"expiresIn": "4h"
}
| Field | Required | Description |
|---|---|---|
url |
Yes | Full file access URL |
expiresIn |
No | Token lifetime: "5m", "1h", "2d", 3600 (default: "4h") |
Response 200 OK:
{
"token": "eyJhbGciOiJSUzI1NiIs...",
"url": "https://cdn.example.com/eyJhbGci.../path/video.mp4",
"signed": true
}
For video files, token expiry is automatically extended to
max(duration * 2, requested_expiresIn).
Get Video Duration
GET /files/duration?file=<access_url>
Headers:
| Header | Required |
|---|---|
API-Key |
Yes |
Response 200 OK:
{
"status": 200,
"duration": 125.5
}
YouTube URLs are automatically detected and duration is fetched via YouTube Data API.
Get Download URL
GET /files/download?file=<access_url>
Headers:
| Header | Required |
|---|---|
API-Key |
Yes |
Response 200 OK:
{
"url": "https://account.r2.cloudflarestorage.com/bucket/...?X-Amz-Algorithm=...",
"filename": "original_filename.mp4",
"file_size": 1024000
}
Register Existing File
Register metadata for a file that's already in your storage bucket (e.g., uploaded outside this platform).
POST /files/register
Headers:
| Header | Required |
|---|---|
API-Key |
Yes |
Content-Type |
application/json |
Body:
{
"filePath": "tenant_id/user_id/destination/file.mp4",
"bucket": "bucket-name",
"userId": "user123",
"requireSignedUrl": true
}
Response 201 Created:
{
"key": "file_id",
"url": "https://cdn.example.com/file_id/path",
"filename": "file.mp4",
"file_type": "video/mp4",
"file_size": 1024000,
"duration": 120.5
}
Delete File
DELETE /files/delete
Headers:
| Header | Required |
|---|---|
API-Key |
Yes |
Content-Type |
application/json |
Body:
{
"file": "https://cdn.example.com/file_id/path"
}
Response 200 OK:
{
"success": true,
"message": "File deleted successfully"
}
Create Stream Copies
Create transcoded stream copies from source video URLs using Cloudflare Stream or VdoCipher.
POST /streams/v1/create
Headers:
| Header | Required |
|---|---|
API-Key |
Yes |
Content-Type |
application/json |
Body:
{
"streams": [
{
"url": "https://cdn.example.com/file_id/video.mp4",
"meta": {
"name": "Lecture 1",
"recordingId": "rec_abc123"
},
"required_signed_url": false,
"provider": "cloudflare_stream"
}
]
}
| Field | Required | Description |
|---|---|---|
streams[].url |
Yes | Source video URL |
streams[].meta |
Yes | Metadata (must include name) |
streams[].required_signed_url |
No | Require signed playback |
streams[].provider |
No | cloudflare_stream (default) or vdocipher |
Response 201 Created:
{
"created": true,
"count": 1,
"ids": ["stream_doc_id"],
"existing": [
{
"id": "existing_id",
"url": "https://cdn.example.com/file_id/video.mp4",
"status": "ready",
"uid": "cf_stream_uid",
"hls_url": "https://stream.cloudflarestream.com/.../manifest/video.m3u8",
"dash_url": "https://stream.cloudflarestream.com/.../manifest/video.mpd",
"thumbnail": "https://cloudflarestream.com/thumbnail.jpg"
}
]
}
Duplicate URLs in the same batch or already in the database are deduplicated. Existing ready streams are returned in the
existingarray.
Stream Status Flow:
pending → processing → downloading → ready
pending → processing → failed (after all retries exhausted)
Webhook Integration
Setting Up Webhooks
Configure a webhook_url in your tenant's storage or streaming settings via the dashboard. The platform sends webhooks for:
- File uploads (direct, TUS, URL)
- Stream encoding status changes
Webhook Signature Verification
All webhooks include an HMAC-SHA256 signature:
Header: Webhook-Signature: sha256=<hex_digest>
Verify in Node.js:
const crypto = require('crypto');
function verifyWebhook(body, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Webhook: File Upload Complete
{
"eventType": "file",
"data": {
"key": "64f1a2b3c4d5e6f7a8b9c0d1",
"recordingUrl": "https://cdn.example.com/file_id/tenant/user/video.mp4",
"filename": "video.mp4",
"file_type": "video/mp4",
"file_size": 1024000,
"user_id": "user123",
"duration": 120.5,
"meta": { "recordingId": "rec_abc123" }
}
}
Webhook: Stream Status Update
{
"eventType": "stream",
"data": {
"uid": "cf_stream_uid",
"provider": "cloudflare_stream",
"url": "https://cdn.example.com/file_id/video.mp4",
"hlsUrl": "https://stream.cloudflarestream.com/.../manifest/video.m3u8",
"dashUrl": "https://stream.cloudflarestream.com/.../manifest/video.mpd",
"thumbnail": "https://cloudflarestream.com/thumbnail.jpg",
"status": "ready",
"duration": 120.5,
"encodingData": { "name": "Lecture 1" }
}
}
Webhook: Recording Complete
{
"eventType": "recording",
"data": {
"key": "507f1f77bcf86cd799439011",
"recordingUrl": "https://media.aauti.com/abc123/tenant123/user456/video.mp4",
"filename": "video.mp4",
"file_type": "video/mp4",
"file_size": 104857600,
"user_id": "user123",
"duration": 245.6
}
}
Webhook Delivery
- Retries: 5 attempts with exponential backoff
- Timeout: 30 seconds per attempt
- Signing: HMAC-SHA256 via
webhook_signing_keyfrom your tenant config - Your endpoint must return HTTP
2xxto acknowledge delivery. Non-2xx triggers a retry.
Accessing Files
Public Files
Uploaded without requireSignedUrl. Directly accessible via access URL:
https://media.aauti.com/{fileId}/{tenantId}/{userId}/{destination}/{filename}
Private Files
Uploaded with requireSignedUrl: true. Requires a signed JWT in the path:
https://media.aauti.com/{jwt_token}/{tenantId}/{userId}/{destination}/{filename}
Private File Access Flow
- Upload with
requireSignedUrl: true - Call
POST /auth/url-tokenwith file's access URL - Use returned signed URL (expires in 4h by default)
Video Playback
<!-- Public video -->
<video src="https://media.aauti.com/fileId/tenant/user/video.mp4" controls></video>
<!-- Private video (use signed URL from /auth/url-token) -->
<video src="https://media.aauti.com/eyJhbGciOi.../tenant/user/video.mp4" controls></video>
Range requests are fully supported. Seek and scrub operations work efficiently without re-downloading the full file.
Domain Whitelisting
The delivery service enforces origin restrictions for your uploaded content:
- Browser requests (with
Referer): hostname must be in your tenant'swhitelisted_domains - Browser requests (no
Referer, image loads): allowed (for email client compatibility) - Mobile apps (
X-App-IDheader): app ID must be in your whitelist - Empty whitelist: no restrictions (all origins allowed)
Configure whitelisted domains in the General Settings page of your dashboard.
Embed & Player Integration
Embedding the Player
Use the embed page in an iframe:
<iframe
src="https://dashboard.example.com/embed?file=FILE_ID&api_key=YOUR_API_KEY"
width="640"
height="360"
frameborder="0"
allow="autoplay; fullscreen; encrypted-media"
allowfullscreen>
</iframe>
Query parameters:
| Param | Description |
|---|---|
file |
File ID (required) |
api_key |
Tenant API key (required) |
player |
Player config ID |
playStream |
true (default) or false |
autoplay |
true / false |
muted |
true / false |
loop |
true / false |
controls |
true / false |
user_id |
User ID for watermark |
user_name |
User name for watermark |
user_email |
User email for watermark |
loader |
Show loading spinner |
postMessage API
Events emitted by the player:
| Event | Payload |
|---|---|
ready |
{ source: 'aauti-embed', event: 'ready', playerType, duration } |
statechange |
{ source: 'aauti-embed', event: 'statechange', state: { playing, muted, currentTime, duration, volume, ended } } |
timeupdate |
{ source: 'aauti-embed', event: 'timeupdate', currentTime, duration } |
ended |
{ source: 'aauti-embed', event: 'ended' } |
error |
{ source: 'aauti-embed', event: 'error', message } |
Actions you can send to the player:
| Action | Data | Description |
|---|---|---|
play / pause |
{ action: 'play' } |
Start / pause playback |
mute / unmute / toggleMute |
{ action: 'mute' } |
Control audio mute state |
seek |
{ action: 'seek', value: 30 } |
Seek to 30 seconds |
volume |
{ action: 'volume', value: 0.5 } |
Set volume (0–1) |
getState |
{ action: 'getState' } |
Request current player state |
Example:
const iframe = document.getElementById('myPlayer');
window.addEventListener('message', (e) => {
if (e.data?.source !== 'aauti-embed') return;
if (e.data.event === 'ready') console.log('Player ready, duration:', e.data.duration);
if (e.data.event === 'ended') console.log('Playback finished');
});
iframe.contentWindow.postMessage({ action: 'play' }, '*');
iframe.contentWindow.postMessage({ action: 'seek', value: 60 }, '*');
Error Reference
Standard Error Format
{
"statusCode": 400,
"message": "Error description",
"error": "BadRequest"
}
Common Status Codes
| Code | Meaning |
|---|---|
200 |
OK |
201 |
Created |
202 |
Accepted (async processing queued) |
206 |
Partial Content (range request) |
304 |
Not Modified (cache hit) |
400 |
Bad Request (missing or invalid parameters) |
401 |
Unauthorized (missing or invalid API key / token) |
403 |
Forbidden (domain not whitelisted / access denied) |
404 |
Not Found (file, tenant, or resource doesn't exist) |
500 |
Internal Server Error |
Common Errors
| Error | Cause |
|---|---|
APP_NOT_WHITELISTED |
Mobile app ID not in tenant whitelist |
ORIGIN_NOT_WHITELISTED |
Browser origin not in tenant whitelist |
SIGNED_URL_REQUIRED |
File requires signed URL but request is unauthenticated |
JWT_REQUIRED |
Non-browser request to protected file without JWT |
Rate Limits & Quotas
| Limit | Value |
|---|---|
| Max file size (URL upload) | Configurable, default 10 GB |
| Max file size (TUS) | Configurable, default 10 GB |
| Webhook retry attempts | 5 |
| Webhook timeout per attempt | 30 seconds |
Quick Reference
| Action | Method | URL | Sync/Async |
|---|---|---|---|
| Direct upload | POST |
{UPLOAD_BASE}/upload/direct-upload |
Sync |
| TUS create | POST |
{UPLOAD_BASE}/files |
Sync |
| TUS chunk | PATCH |
{UPLOAD_BASE}/files/{id} |
Sync |
| TUS status | HEAD |
{UPLOAD_BASE}/files/{id} |
Sync |
| TUS cancel | DELETE |
{UPLOAD_BASE}/files/{id} |
Sync |
| Upload from URL | POST |
{UPLOAD_BASE}/upload/from-url |
Async |
| Get signed URL | POST |
{API_BASE}/auth/url-token |
Sync |
| Get duration | GET |
{API_BASE}/files/duration?file={url} |
Sync |
| Download file | GET |
{API_BASE}/files/download?file={url} |
Sync |
| Register file | POST |
{API_BASE}/files/register |
Sync |
| Delete file | DELETE |
{API_BASE}/files/delete |
Sync |
| Create streams | POST |
{API_BASE}/streams/v1/create |
Async |
| Embed player | GET |
{DASHBOARD_BASE}/embed?file={id}&api_key={key} |
Sync |
Aauti Media Platform — Integration Guide