Quickstart
Sign up, upload, embed, delete
- Sign in at
/loginwith an email code. Rend creates your workspace automatically. - Choose a plan from the Billing page. Billable uploads and API-key creation stay disabled until billing is ready.
- Create an API key from the Rend dashboard with
upload,read,delete, andanalyticsscopes. - Upload a video with resumable
POST /v1/uploads, legacyPOST /v1/videos, orclient.uploadAsset. - Poll the asset until
playable_stateisopener_readyorhls_ready. - Fetch
/api/player/{assetId}from the site and embed the returned same-origin source. - Delete the asset with
DELETE /v1/assets/{assetId}when it is no longer needed.
import { readFile } from "node:fs/promises";
import { RendClient } from "@rend-sdk/client";
// 1. Sign in at https://rend.so/login with an email code.
// 2. Add a payment method for automatic pay-as-you-go billing.
// 3. Create an API key with upload, read, delete, and analytics scopes.
const client = new RendClient({
apiKey: process.env.REND_API_KEY,
});
const file = await readFile("video.mp4");
const upload = await client.uploadAsset(file, {
contentType: "video/mp4",
contentLength: file.byteLength,
});
const asset = await client.waitForPlayableAsset(upload.asset_id, {
timeoutMs: 180_000,
intervalMs: 1_000,
});
const bootstrap = await client.getPlaybackBootstrap(asset.asset_id);
const source =
bootstrap.manifest_url ?? bootstrap.playback_url ?? bootstrap.opener_url;
const contentType =
bootstrap.manifest_content_type ??
bootstrap.playback_content_type ??
bootstrap.opener_content_type ??
"video/mp4";
if (!source) throw new Error("No playable source returned");
const playbackUrl = new URL(source, "https://rend.so").toString();
const embedHtml =
'<video controls playsinline preload="metadata">' +
`<source src="${playbackUrl}" type="${contentType}">` +
"</video>";
console.log(embedHtml);
await client.deleteAsset(asset.asset_id);Agents
Hand Rend to a coding agent
Rend exposes a small, stable surface for agents: llms.txt for discovery, llms-full.txt for larger context windows, OpenAPI JSON for the public contract, and the generated TypeScript SDK.
Use this prompt when you want an agent to add upload, playback, error handling and a cleanup smoke test to an app without exposing API keys in browser code.
- Authenticated API calls go to
https://api.rend.sowith bearer auth. - Browser playback uses
https://rend.so/embed/{assetId}or/api/player/{assetId}on the site origin. - Agents should prefer the SDK for app code and OpenAPI for generated tools.
MCP
Give your agent Rend tools
Use @rend-sdk/mcp when Claude, Cursor, or another MCP-compatible agent should upload videos, inspect assets, fetch playback links, read analytics, and clean up test assets through Rend without first writing app integration code.
Cursor
Install with one click
Opens Cursor with the Rend MCP config prefilled. Review the command, paste your Rend API key into the env block, then approve the install.
Any MCP client
Run the package with npx
Clients that accept a command and args can launch the server directly. KeepREND_API_KEY in the client env, never in chat prompts.
npx -y @rend-sdk/mcp{
"mcpServers": {
"rend": {
"command": "npx",
"args": ["-y", "@rend-sdk/mcp"],
"env": {
"REND_API_KEY": "rend_live_...",
"REND_API_BASE_URL": "https://api.rend.so",
"REND_SITE_BASE_URL": "https://rend.so"
}
}
}
}Upload from local files
Agents can send a local MP4 or QuickTime file through the public upload API.
Inspect asset state
Agents can list assets, fetch lifecycle state, and wait until playback is ready.
Return playback links
Agents can fetch tokenless bootstrap output with hosted embed and watch URLs.
Clean up and measure
Agents can delete assets and read playback request analytics with scoped keys.
- Grant API keys only the scopes the workflow needs:
upload,read,delete, andanalytics. - Pass the API key through the MCP client
envblock, not as a tool argument or prompt text. - Override
REND_API_BASE_URLandREND_SITE_BASE_URLfor local or private deployments. - Uploads check local file size before sending data and reject detectable non-video files.
- Playback tool output includes hosted embed/watch URLs and redacts secret-bearing fields.
Local development
Point the MCP server at your local API and site when developing Rend itself, then run the smoke to upload a fixture, wait for playback, fetch analytics, delete the asset, and check that tool output stays public-safe.
{
"mcpServers": {
"rend-local": {
"command": "node",
"args": ["packages/mcp/dist/bin/rend-mcp.js"],
"env": {
"REND_API_KEY": "rend_test_...",
"REND_API_BASE_URL": "http://127.0.0.1:4000",
"REND_SITE_BASE_URL": "http://127.0.0.1:3000"
}
}
}
}bun install
bun run mcp:smokeSDK
Use the generated TypeScript SDK
The SDK lives in packages/sdk and is generated from the public OpenAPI contract. It uses the API base URL for authenticated asset calls and the site base URL for browser-safe playback bootstrap.
apiKeyis sent only to the API server as a bearer token.apiBaseUrldefaults tohttps://api.rend.so.siteBaseUrldefaults tohttps://rend.so.waitForPlayableAssetreturns when the asset can be played or throws on timeout.
import { RendClient, RendApiError } from "@rend-sdk/client";
const rend = new RendClient({
apiKey: process.env.REND_API_KEY,
apiBaseUrl: process.env.REND_API_BASE_URL,
siteBaseUrl: process.env.REND_SITE_BASE_URL,
});
try {
const assets = await rend.listAssets({ limit: 10 });
const analytics = await rend.getPlaybackAnalytics(assets.assets[0].asset_id, {
windowSeconds: 3600,
});
console.log(analytics.request_count);
} catch (error) {
if (error instanceof RendApiError) {
console.error(error.status, error.body);
}
throw error;
}curl
Use the public API directly
The public control-plane shape is /v1/uploads, /v1/videos, /v1/assets, /v1/assets/{assetId}/events, and /v1/assets/{assetId}/analytics/playback. Browser playback uses the site route /api/player/{assetId}.
The curl example uses the legacy raw route for scripts and stream-only callers. New browser and large-file integrations should use the resumable flow below.
export REND_API_KEY="rend_live_..."
export REND_API_BASE_URL="https://api.rend.so"
export REND_SITE_BASE_URL="https://rend.so"
# First: sign in at https://rend.so/login, add a payment method, and create an API key.
# Compatibility: one-shot raw uploads remain supported at /v1/videos.
curl -fsS -X POST "$REND_API_BASE_URL/v1/videos" \
-H "authorization: Bearer $REND_API_KEY" \
-H "content-type: video/mp4" \
--data-binary @video.mp4 > upload.json
ASSET_ID="$(jq -r '.asset_id' upload.json)"
until curl -fsS "$REND_API_BASE_URL/v1/assets/$ASSET_ID" \
-H "authorization: Bearer $REND_API_KEY" > asset.json &&
[ "$(jq -r '.playable_state' asset.json)" = "hls_ready" ]; do
sleep 1
done
curl -fsS "$REND_SITE_BASE_URL/api/player/$ASSET_ID" > playback.json
curl -fsS "$REND_API_BASE_URL/v1/assets/$ASSET_ID/analytics/playback?window_seconds=3600" \
-H "authorization: Bearer $REND_API_KEY" > analytics.json
curl -fsS -X DELETE "$REND_API_BASE_URL/v1/assets/$ASSET_ID" \
-H "authorization: Bearer $REND_API_KEY"Uploads
Direct, resumable multipart uploads
Create a session through the Rend API, then upload its 16 MiB parts directly to the returned storage URLs. Source bytes do not pass through the Rend API. A session allows at most six concurrent part uploads, and each signing request accepts at most 10 parts.
- Send a stable
Idempotency-Keywhen creating a session so uncertain requests are safe to retry. - Send the base64-encoded SHA-256 checksum for every part when signing and completing it.
- Use
GET /v1/uploads/{uploadId}to recover provider-confirmed parts after an interruption. - Use
DELETE /v1/uploads/{uploadId}to abort an unfinished upload and release reserved storage. - Keep API keys on a trusted server. Browser dashboards should use a short-lived, upload-scoped token.
POST /v1/uploads
Authorization: Bearer $REND_API_KEY
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"content_type": "video/mp4",
"content_length": 42881500,
"filename": "video.mp4"
}
# Split the file into the returned 16 MiB part_size.
# For batches of at most 10 parts, compute each base64 SHA-256 digest:
POST /v1/uploads/{uploadId}/parts
{
"parts": [{ "part_number": 1, "checksum_sha256": "base64-sha256=" }]
}
# PUT each part directly to its returned URL with the returned headers.
# Keep the ETag, then resume safely by reading provider-confirmed parts:
GET /v1/uploads/{uploadId}
POST /v1/uploads/{uploadId}/complete
{
"parts": [{
"part_number": 1,
"etag": "returned-etag",
"checksum_sha256": "base64-sha256="
}]
}
# Cancel an unfinished session and release its reserved storage:
DELETE /v1/uploads/{uploadId}Auth
API keys and scopes
Authenticated API calls use a bearer API key. Store the key server-side, pass it to the SDK as apiKey, and avoid exposing it in browser bundles.
Authorization: Bearer $REND_API_KEY| Scope | Allows |
|---|---|
| upload | Upload a source video. |
| read | List assets, fetch asset details, poll lifecycle events, and bootstrap playback. |
| delete | Delete an asset and its Rend-owned origin objects. |
| analytics | Fetch playback request analytics. |
Playback
Tokenless same-origin browser playback
Browser code does not call the API server for playback. Call the site bootstrap route and use the returned same-origin artifact URL in a video element or the Rend player. The JSON response does not expose provider URLs or signed query strings.
GET https://rend.so/api/player/018f52b2-5401-7f3b-ae2e-4923f4d62120
{
"status": "ready",
"asset_id": "018f52b2-5401-7f3b-ae2e-4923f4d62120",
"source_state": "uploaded",
"playable_state": "hls_ready",
"playback_url": "/api/player/018f52b2-5401-7f3b-ae2e-4923f4d62120/artifact/hls/master.m3u8",
"playback_content_type": "application/vnd.apple.mpegurl",
"manifest_url": "/api/player/018f52b2-5401-7f3b-ae2e-4923f4d62120/artifact/hls/master.m3u8",
"manifest_content_type": "application/vnd.apple.mpegurl",
"playback_token_expires_at": 1781432100,
"ttl_seconds": 900,
"prefetch_hints": [
{
"artifact_path": "hls/360p/init_360p.mp4",
"url": "/api/player/018f52b2-5401-7f3b-ae2e-4923f4d62120/artifact/hls/360p/init_360p.mp4",
"content_type": "video/mp4"
}
]
}- For a bare
videoelement, usemanifest_urlfirst when present. The hosted Rend player starts withopener_urland hands off to HLS. - Artifact URLs are relative to the Rend site origin.
- The hosted embed page is
/embed/{assetId}and/watch/{assetId}aliases the same player.
Billing
Autumn billing and usage limits
Hosted Rend uses Autumn as the source of truth for the pay-as-you-go plan, usage, checkout, and the billing portal. New workspaces are created on first email-OTP sign-in and synced to Autumn by organization ID. API-key creation and uploads can return billing_required or limit_exceeded until a plan is active and within limits; upload denials happen before the source body is accepted.
- Delivery is billed at $0.001 per minute of viewer watch time.
- Storage is billed at $0.003 per stored video minute per month and prorated precisely.
- Every video resolution uses the same rates.
- Upload/source bytes remain local safety limits and are not customer-facing Autumn meters.
- Already-issued playback artifact URLs do not call Autumn, Postgres, or the Rend API on the playback hot path.
Errors
Common states and responses
| State | Where | Meaning |
|---|---|---|
| billing_required | Dashboard API key creation returns 402 | The workspace needs an active plan before creating API keys. |
| limit_exceeded | POST /v1/uploads or POST /v1/videos returns 403 | Billing is missing, inactive, or out of plan balance before the upload body is accepted. |
| not_playable | GET /api/player/{assetId} returns 409 | The asset exists, but no opener or HLS artifact is ready yet. |
| suspended | API calls return 403, or asset summaries include suspension fields | The asset or organization is blocked from normal API use. |
| unauthorized | API calls return 401 | The bearer API key is missing, malformed, revoked, or invalid. |
| upload too large | POST /v1/uploads or POST /v1/videos returns 413 | The upload exceeds the configured maximum size. |
| deleted | Asset reads or playback return unavailable/not found | The asset was deleted; bootstrap and artifact paths should no longer play. |
Local
Local Docker development path
The local stack runs Postgres, Redis, ClickHouse, MinIO, the API, media worker, and edge service. Create API keys through the local dashboard or run the SDK smoke, which creates a local smoke key when REND_API_KEY is not set.
bun install
cp .env.local.example .env.local
bun run backend:docker:build
bun run backend:docker:up
bun run dev:site
bun run sdk:integration-smokeProduction
Production profile notes
- Use
REND_ENV=productionfor hosted production services. - Set
REND_SELF_SERVE_SIGNUP_ENABLED=trueintentionally for public self-serve signup. - Configure Better Auth with secure production secrets and Resend email delivery.
- Keep API keys and provider credentials out of
NEXT_PUBLIC_*values. - Set
REND_API_BASE_URLfor server-side API calls andREND_SITE_BASE_URLfor playback bootstrap clients. - Run
bun run launch:self-serve-readinessandbun run launch:gate -- --mode production-checkbefore public V1 launch. - Run
bun run openapi:checkafter changing the public contract or SDK generator.
Reference
OpenAPI and SDK source
- OpenAPI JSON is the canonical public API contract served by the site.
- packages/sdk contains the generated TypeScript client.
- llms.txt lists stable docs anchors for agents.