Documentation

Upload a video, get a playable URL

This is the public integration path for Rend: API-key control-plane calls for assets, anonymous same-origin site routes for browser playback, and MCP tools for agents.

or
Browse docs

Quickstart

Sign up, upload, embed, delete

  1. Sign in at /login with an email code. Rend creates your workspace automatically.
  2. Choose a plan from the Billing page. Billable uploads and API-key creation stay disabled until billing is ready.
  3. Create an API key from the Rend dashboard with upload, read, delete, and analytics scopes.
  4. Upload a video with resumable POST /v1/uploads, legacy POST /v1/videos, or client.uploadAsset.
  5. Poll the asset until playable_state is opener_ready or hls_ready.
  6. Fetch /api/player/{assetId} from the site and embed the returned same-origin source.
  7. Delete the asset with DELETE /v1/assets/{assetId} when it is no longer needed.
quickstart.ts
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.so with 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.

Add to Cursor

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
mcp-config.json
{
  "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, and analytics.
  • Pass the API key through the MCP client env block, not as a tool argument or prompt text.
  • Override REND_API_BASE_URL and REND_SITE_BASE_URL for 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.

mcp-local-config.json
{
  "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"
      }
    }
  }
}
mcp-smoke.sh
bun install
bun run mcp:smoke

SDK

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.

  • apiKey is sent only to the API server as a bearer token.
  • apiBaseUrl defaults to https://api.rend.so.
  • siteBaseUrl defaults to https://rend.so.
  • waitForPlayableAsset returns when the asset can be played or throws on timeout.
sdk-usage.ts
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.

curl-flow.sh
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-Key when 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.
resumable-upload.http
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.

auth-header.txt
Authorization: Bearer $REND_API_KEY
ScopeAllows
uploadUpload a source video.
readList assets, fetch asset details, poll lifecycle events, and bootstrap playback.
deleteDelete an asset and its Rend-owned origin objects.
analyticsFetch 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.

playback-bootstrap.json
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 video element, use manifest_url first when present. The hosted Rend player starts with opener_url and 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

StateWhereMeaning
billing_requiredDashboard API key creation returns 402The workspace needs an active plan before creating API keys.
limit_exceededPOST /v1/uploads or POST /v1/videos returns 403Billing is missing, inactive, or out of plan balance before the upload body is accepted.
not_playableGET /api/player/{assetId} returns 409The asset exists, but no opener or HLS artifact is ready yet.
suspendedAPI calls return 403, or asset summaries include suspension fieldsThe asset or organization is blocked from normal API use.
unauthorizedAPI calls return 401The bearer API key is missing, malformed, revoked, or invalid.
upload too largePOST /v1/uploads or POST /v1/videos returns 413The upload exceeds the configured maximum size.
deletedAsset reads or playback return unavailable/not foundThe 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.

local-dev.sh
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-smoke

Production

Production profile notes

  • Use REND_ENV=production for hosted production services.
  • Set REND_SELF_SERVE_SIGNUP_ENABLED=true intentionally 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_URL for server-side API calls and REND_SITE_BASE_URL for playback bootstrap clients.
  • Run bun run launch:self-serve-readiness and bun run launch:gate -- --mode production-check before public V1 launch.
  • Run bun run openapi:check after 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.