Workflows

Publish to Rad

Upload video from a URL or local file, transcode it, and publish on Rad TV.

Publishing a video to Rad TV takes your source file through ingestion, transcoding, and publication. You can do this with a single compound mutation or step-by-step for full control.

Option 1: Single Call with publishVideo

The publishVideo mutation handles the entire pipeline. It supports two modes:

Mode 1: Server-Side Download (URL)

Provide a sourceUrl and the server downloads, ingests, transcodes, and publishes the video for you.

curl -X POST https://api.rad.live/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "query": "mutation($input: PublishVideoInput!) { publishVideo(input: $input) { content { id metadata { title } } job { id status } } }",
    "variables": {
      "input": {
        "title": "My Video",
        "summary": "A great video about something.",
        "sourceUrl": "https://example.com/video.mp4",
        "contentType": "landscape",
        "publish": true
      }
    }
  }'
const response = await fetch('https://api.rad.live/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_API_KEY',
  },
  body: JSON.stringify({
    query: `
      mutation($input: PublishVideoInput!) {
        publishVideo(input: $input) {
          content { id metadata { title } }
          job { id status }
        }
      }
    `,
    variables: {
      input: {
        title: 'My Video',
        summary: 'A great video about something.',
        sourceUrl: 'https://example.com/video.mp4',
        contentType: 'landscape',
        publish: true,
      },
    },
  }),
});

const { data } = await response.json();
console.log(data.publishVideo.content.id);
import requests

response = requests.post(
    "https://api.rad.live/graphql",
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "query": """
        mutation($input: PublishVideoInput!) {
          publishVideo(input: $input) {
            content { id metadata { title } }
            job { id status }
          }
        }
        """,
        "variables": {
            "input": {
                "title": "My Video",
                "summary": "A great video about something.",
                "sourceUrl": "https://example.com/video.mp4",
                "contentType": "landscape",
                "publish": True,
            }
        },
    },
)

result = response.json()["data"]["publishVideo"]
print(result["content"]["id"])

Mode 2: Local File Upload

Provide filename and size instead of sourceUrl. The server creates an ingestion session and returns an upload endpoint.

# Step 1: Create the ingestion session
curl -X POST https://api.rad.live/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "query": "mutation($input: PublishVideoInput!) { publishVideo(input: $input) { content { id } upload { endpoint } } }",
    "variables": {
      "input": {
        "title": "My Local Video",
        "filename": "my-video.mp4",
        "size": 524288000,
        "contentType": "landscape",
        "publish": true
      }
    }
  }'

# Step 2: Upload the file to the returned endpoint
curl -X POST <UPLOAD_ENDPOINT> \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@my-video.mp4"
// Step 1: Create ingestion session
const res = await fetch('https://api.rad.live/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_API_KEY',
  },
  body: JSON.stringify({
    query: `
      mutation($input: PublishVideoInput!) {
        publishVideo(input: $input) {
          content { id }
          upload { endpoint }
        }
      }
    `,
    variables: {
      input: {
        title: 'My Local Video',
        filename: 'my-video.mp4',
        size: 524288000,
        contentType: 'landscape',
        publish: true,
      },
    },
  }),
});

const { data } = await res.json();
const uploadEndpoint = data.publishVideo.upload.endpoint;

// Step 2: Upload the file
const formData = new FormData();
formData.append('file', videoFile);

await fetch(uploadEndpoint, {
  method: 'POST',
  headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
  body: formData,
});

// Auto-finalize: server automatically transcodes and publishes after upload completes
import requests

# Step 1: Create ingestion session
response = requests.post(
    "https://api.rad.live/graphql",
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer YOUR_API_KEY",
    },
    json={
        "query": """
        mutation($input: PublishVideoInput!) {
          publishVideo(input: $input) {
            content { id }
            upload { endpoint }
          }
        }
        """,
        "variables": {
            "input": {
                "title": "My Local Video",
                "filename": "my-video.mp4",
                "size": 524288000,
                "contentType": "landscape",
                "publish": True,
            }
        },
    },
)

result = response.json()["data"]["publishVideo"]
upload_endpoint = result["upload"]["endpoint"]

# Step 2: Upload the file
with open("my-video.mp4", "rb") as f:
    requests.post(
        upload_endpoint,
        headers={"Authorization": "Bearer YOUR_API_KEY"},
        files={"file": f},
    )

# Auto-finalize: server transcodes and publishes after upload

Auto-finalize: In Mode 2, the server stores your workflow parameters (layout, enhance, publish) and automatically triggers transcoding and publication after the upload completes. No additional API calls needed.

PublishVideoInput Fields

FieldTypeRequiredDescription
titleStringYesContent title
summaryStringNoContent description
channelDIDNoChannel DID (defaults to your channel)
sourceUrlURLMode 1URL to download video from
filenameStringMode 2Local file name
sizeIntMode 2File size in bytes
contentTypeVideoContentTypeNolandscape, portrait, vr, or short
layoutContentLayoutNoLANDSCAPE, PORTRAIT, or VR
enhanceBooleanNoEnable AI upscaling + audio upmixing (Creator+)
publishBooleanNoPublish immediately after transcoding
releaseDateStringNoScheduled release date (ISO 8601)
thumbnailUrlURLNoCustom thumbnail URL

Option 2: Step-by-Step

For full control, use individual mutations:

Create content

mutation {
  createContent(input: {
    metadata: { title: "My Video", summary: "Description" }
    hints: { type: FEATURE }
  }) {
    id
  }
}

Upload a video asset

mutation {
  createContentAsset(
    id: "did:rad.live:content/feature/123"
    input: { filename: "video.mp4", size: 524288000 }
  ) {
    id
    endpoint
  }
}

Then upload the file to the returned TUS endpoint. See Uploads for details.

Submit for transcoding

mutation {
  submitContentForProcessing(
    id: "did:rad.live:content/feature/123"
    input: { assetName: "video.mp4", layout: LANDSCAPE, enhance: false }
  ) {
    job { id status }
  }
}

Publish

mutation {
  publishContent(id: "did:rad.live:content/feature/123") {
    id
    metadata { title }
  }
}

Checking Processing Status

After submitting for processing, you can poll the transcode job or use waitForProcessing:

mutation {
  waitForProcessing(input: {
    contentId: "did:rad.live:content/feature/123"
    timeoutSeconds: 300
  }) {
    status
    job { status progress }
  }
}

The waitForProcessing mutation blocks server-side until the job completes, errors, or times out — eliminating the need for client-side polling loops.

Next steps

  • AI Upscaling — Add enhance: true for 4K/8K upscaling
  • Output Assets — See what you get after transcoding
  • Uploads — Deep dive into the TUS upload protocol

On this page