RenderIO
API referenceStorage destinations

Bring Your Own Bucket

Configure an S3-compatible bucket as the output destination for RenderIO commands, test it, and retrieve public or private outputs.

Bring Your Own Bucket

Business accounts can send FFmpeg and yt-dlp outputs directly to an AWS S3 or S3-compatible bucket. RenderIO encrypts the supplied credentials, uses them only to create short-lived upload URLs, and does not copy externally stored outputs into RenderIO-managed storage.

Every completed external output records a durable object key and S3 URI. A public HTTP URL is also returned when the destination has a working public_base_url. You can therefore find an output again by polling its original command ID—rerunning the media job is not required.

The complete flow is:

  1. Create bucket-scoped credentials.
  2. Create and test a RenderIO storage destination.
  3. Add its storage_destination_id to a command.
  4. Save the returned command_id and poll the command to SUCCESS.
  5. Read storage_url, external_uri, or external_object_key from the completed output.

1. Create least-privilege credentials

Grant access only to the bucket and optional prefix RenderIO will use. The connection test writes, verifies, and deletes a temporary object. Production uploads may use multipart upload for outputs larger than 16 MiB.

Never send an S3 or R2 access key to RenderIO support by email or chat. Enter credentials only through the authenticated RenderIO API.

Cloudflare R2

In Cloudflare, open R2 Object Storage → Manage R2 API Tokens, then create a token with:

  • Permission: Object Read & Write
  • Bucket scope: only the bucket RenderIO will use
  • TTL: the shortest practical lifetime for temporary credentials, or your normal rotation period for production credentials

Use these destination settings:

{
  "endpoint_url": "https://ACCOUNT_ID.r2.cloudflarestorage.com",
  "region": "auto",
  "bucket": "YOUR_BUCKET",
  "path_style": true
}

The S3 API endpoint is not a public download URL. If the bucket is public, configure a working R2 custom domain or public bucket URL separately as public_base_url.

AWS S3

For AWS S3, this policy is a suitable starting point. Replace the bucket and prefix before applying it:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:AbortMultipartUpload"
      ],
      "Resource": "arn:aws:s3:::YOUR_BUCKET/renderio/*"
    }
  ]
}

s3:GetObject and s3:DeleteObject are required by the connection test. The production upload path uses s3:PutObject and may require s3:AbortMultipartUpload to clean up an interrupted multipart upload.

2. Create a destination

POST /api/v1/storage-destinations
curl -X POST https://renderio.dev/api/v1/storage-destinations \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: ffsk_your_api_key_here" \
  -d '{
    "name": "production-outputs",
    "endpoint_url": "https://ACCOUNT_ID.r2.cloudflarestorage.com",
    "region": "auto",
    "bucket": "your-company-media",
    "prefix": "renderio",
    "path_style": true,
    "credentials": {
      "access_key_id": "YOUR_ACCESS_KEY_ID",
      "secret_access_key": "YOUR_SECRET_ACCESS_KEY"
    }
  }'

A successful request returns 201 Created:

{
  "storage_destination_id": "sd_0123456789abcdef0123456789abcdef",
  "name": "production-outputs",
  "endpoint_url": "https://ACCOUNT_ID.r2.cloudflarestorage.com",
  "region": "auto",
  "bucket": "your-company-media",
  "prefix": "renderio",
  "public_base_url": null,
  "path_style": true,
  "access_key_id_masked": "****MPLE",
  "disabled": false,
  "created_at": "2026-09-10 12:00:00",
  "updated_at": "2026-09-10 12:00:00"
}

Save storage_destination_id for command requests. Secret credentials are encrypted and are never returned; only a masked access key ID is shown.

Destination fields

FieldRequiredDescription
nameYesUnique label for this destination.
endpoint_urlYesHTTPS S3-compatible API endpoint. Raw IP addresses are rejected.
regionYesSigning region, such as us-east-1 or auto for R2.
bucketYesDestination bucket name.
prefixNoKey prefix. Leading and trailing slashes are removed.
public_base_urlNoWorking public or CDN base URL for the bucket root. Omit it for a private bucket.
path_styleNoWhether the bucket belongs in the URL path instead of the hostname. Use true for R2. Defaults to false.
credentialsYesaccess_key_id, secret_access_key, and optional session_token.

endpoint_url, region, and bucket are immutable. Create a new destination to change them.

Public URLs

Set public_base_url only when that URL already maps the bucket root to public HTTP access. For example:

{
  "public_base_url": "https://media.example.com"
}

For an object key such as renderio/COMMAND_ID/output.mp4, RenderIO then records:

https://media.example.com/renderio/COMMAND_ID/output.mp4

Do not use the authenticated S3 API endpoint as public_base_url. The connection test validates S3 write/read/delete access but does not validate public HTTP delivery, so test the public domain separately.

The public URL is captured when the command completes. Adding or changing public_base_url later does not backfill storage_url on older commands; their persisted external_object_key remains available and can be joined to the new base URL by your application.

3. Test the connection

POST /api/v1/storage-destinations/:storageDestinationId/test
curl -X POST \
  https://renderio.dev/api/v1/storage-destinations/sd_0123456789abcdef0123456789abcdef/test \
  -H "X-API-KEY: ffsk_your_api_key_here"

A successful test returns HTTP 200:

{ "ok": true }

The test writes, reads metadata for, and deletes a small marker object under {prefix}/__renderio_test/. Confirm that no marker remains after the request.

A provider-side permission or connectivity failure also returns HTTP 200, with a credential-safe explanation:

{
  "ok": false,
  "error": "Upload failed with status 403"
}

Always check the ok field rather than relying only on the HTTP status.

4. Send an output to the bucket

Add storage_destination_id at the top level of a single FFmpeg, chained FFmpeg, yt-dlp, or yt-dlp-plus-FFmpeg request:

curl -X POST https://renderio.dev/api/v1/ytdlp-download \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: ffsk_your_api_key_here" \
  -d '{
    "input_urls": {
      "in_video": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
    },
    "format_selector": "720p",
    "storage_destination_id": "sd_0123456789abcdef0123456789abcdef"
  }'

The submission response contains the command ID:

{
  "command_id": "550e8400-e29b-41d4-a716-446655440000"
}

BYOB is supported by:

  • POST /api/v1/run-ffmpeg-command
  • POST /api/v1/run-chained-ffmpeg-commands
  • POST /api/v1/run-multiple-ffmpeg-commands—set the destination on each command object that should use it
  • POST /api/v1/ytdlp-download
  • POST /api/v1/run-ytdlp-command

Objects use this deterministic key layout:

{prefix}/{command_id}/{filename}

5. Retrieve an output later

Save the command_id. At any later time, use any valid API key belonging to the same RenderIO account to retrieve the command:

curl \
  https://renderio.dev/api/v1/commands/550e8400-e29b-41d4-a716-446655440000 \
  -H "X-API-KEY: ffsk_your_api_key_here"

Poll until status is SUCCESS or FAILED. A successful private-bucket output looks like:

{
  "command_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "SUCCESS",
  "command_type": "YTDLP_COMMAND",
  "output_files": {
    "out_video": {
      "file_id": "d98c737c-1371-4cc6-bbca-d04bedb41fd8",
      "filename": "video.mp4",
      "status": "STORED",
      "rendi_store_type": "OUTPUT",
      "storage_location": "EXTERNAL",
      "external_uri": "s3://your-company-media/renderio/550e8400-e29b-41d4-a716-446655440000/video.mp4",
      "external_object_key": "renderio/550e8400-e29b-41d4-a716-446655440000/video.mp4",
      "storage_url": null,
      "is_deleted": false,
      "size_mbytes": 12.5
    }
  }
}

The command result and external object key remain available even if the destination is later disabled. No media rerun is required.

Choose the usable locator in this order:

const file = result.output_files.out_video;

if (file.storage_url) {
  console.log("Public URL:", file.storage_url);
} else {
  console.log("S3 URI:", file.external_uri);
  console.log("Object key:", file.external_object_key);
}

Download from a private R2 bucket

Use the returned external_uri or external_object_key with credentials that have read access to the bucket. For example, after placing credentials in AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables:

aws s3 cp \
  --endpoint-url "https://ACCOUNT_ID.r2.cloudflarestorage.com" \
  "s3://YOUR_BUCKET/renderio/COMMAND_ID/video.mp4" \
  "./video.mp4"

RenderIO does not currently issue an HTTP download URL for a private BYOB object. Your application can read it with its own S3 SDK, generate its own short-lived read URL, or deliver it through its existing CDN.

Download from a public bucket

When public_base_url was configured before completion, the same result includes a public URL:

{
  "storage_location": "EXTERNAL",
  "external_object_key": "renderio/COMMAND_ID/video.mp4",
  "storage_url": "https://media.example.com/renderio/COMMAND_ID/video.mp4"
}

Manage destinations

All operations require X-API-KEY authentication and a Business account. A destination can only be accessed and used by the account that created it.

MethodPathDescription
GET/api/v1/storage-destinationsList destinations, including disabled destinations.
POST/api/v1/storage-destinationsCreate a destination.
GET/api/v1/storage-destinations/:storageDestinationIdGet a destination.
PATCH/api/v1/storage-destinations/:storageDestinationIdUpdate its name, prefix, public URL, path style, or credentials.
DELETE/api/v1/storage-destinations/:storageDestinationIdDisable it for future jobs. Existing bucket objects and command metadata remain.
POST/api/v1/storage-destinations/:storageDestinationId/testTest write, verification, and cleanup.

Rotate credentials or change the public URL with PATCH:

curl -X PATCH \
  https://renderio.dev/api/v1/storage-destinations/sd_0123456789abcdef0123456789abcdef \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: ffsk_your_api_key_here" \
  -d '{
    "public_base_url": "https://media.example.com",
    "credentials": {
      "access_key_id": "NEW_ACCESS_KEY_ID",
      "secret_access_key": "NEW_SECRET_ACCESS_KEY"
    }
  }'

Disable a destination when it should no longer accept new outputs:

curl -X DELETE \
  https://renderio.dev/api/v1/storage-destinations/sd_0123456789abcdef0123456789abcdef \
  -H "X-API-KEY: ffsk_your_api_key_here"
{ "deleted": true }

Disabling a destination does not revoke the provider credential. Revoke or rotate that credential separately in AWS, R2, or your storage provider.

Ownership and deletion

The customer owns every object written to a BYOB destination. RenderIO therefore:

  • does not bill those bytes as RenderIO-managed storage;
  • does not serve private bucket objects;
  • does not delete or tombstone external objects when /commands/:commandId/files is called;
  • does not delete bucket objects when a destination is disabled.

Use bucket lifecycle policies or your own S3 tooling to delete external objects. The persisted RenderIO command result continues to identify the original bucket key.

Error responses

StatusMeaning
400Invalid destination configuration or request body.
401Missing or invalid X-API-KEY.
403The account is not on the Business plan.
404The destination does not exist or belongs to another account.
409Duplicate destination name, or a command references a disabled destination.
422A command supplied an invalid storage_destination_id value.
429Rate limit exceeded.

Connection-test provider failures are the exception: they return HTTP 200 with { "ok": false, "error": "..." }.

Support checklist

When asking RenderIO support for help, send only:

  • the storage_destination_id;
  • the affected command_id;
  • the approximate time of the request;
  • the safe ok: false error or command error message.

Never send a RenderIO API key, S3/R2 access key, secret access key, session token, capability token, or presigned URL.

On this page