All Collections
Render API
Render API with Node.js
Render API with Node.js

Learn how to use Helio's REST API with Node.js

Lailo Sadiki avatar
Written by Lailo Sadiki
Updated over a week ago

Preparation

  1. Create a Helio account on https://helio.exchange

  2. Request your personal API Key via the support chat or https://helio.exchange/help/contact

  3. Install Rclone on your operating system https://rclone.org/downloads


Environment Variables

Create a .env file in the root of your project and add the following values. Make sure you complete all the missing values and update the placeholder values accordingly.

HELIO_ID_URL=https://id.helio.exchange/auth/realms/helio/protocol/openid-connect/token
HELIO_ID_YOUR_EMAIL=
HELIO_ID_YOUR_PASSWORD=
HELIO_ID_CLIENT_ID=
HELIO_ID_CLIENT_SECRET=

HELIO_RENDER_API_URL=https://render-api.helio.exchange/v1RCLONE_PATH=/path/to/rclone

PROJECT_PATH=/path/to/project/root
RENDER_RESULT_PATH=/path/to/download/results
PROJECT_FILE=file_name


Environment Requirements

You'll need to have Node.js installed with a version >= 20. To make sure you have the right version and that everything works as expected, run the following code which should output the currently installed version.

❯ node --version
v20.10.0


Write Your Script

Create a helio.mjs file in the root of your project. Fill it up with the following code sections. To load the .env file and run the script, execute the following code.

node --env-file .env helio.mjs


Root Imports

These imports and the async exec are required in the rest of the script. Add this to the top of the file.

import util from 'util'
import child_process from 'child_process'
import fs from 'fs'

const exec = util.promisify(child_process.exec)

Get Access Token

Call the Helio ID to log in and get the access token.

let accessToken

try {
const response = await fetch(process.env.HELIO_ID_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'password',
scope: 'openid',
username: process.env.HELIO_ID_YOUR_EMAIL,
password: process.env.HELIO_ID_YOUR_PASSWORD,
client_id: process.env.HELIO_ID_CLIENT_ID,
client_secret: process.env.HELIO_ID_CLIENT_SECRET,
}),
})
if (!response.ok) {
const error = await response.json()
throw new Error(`HTTP error! status: ${response.status}: ${error.message}`)
}
const data = await response.json()
accessToken = data['access_token']
console.info('Success: access token: ', accessToken)
} catch (error) {
console.error('Error: access token:', error)
}

Get Your User

Get your authenticated user ID to be able to fetch your teams.

try {
const response = await fetch(process.env.HELIO_RENDER_API_URL + `/user`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})
if (!response.ok) {
const error = await response.json()
throw new Error(`HTTP error! status: ${response.status}: ${error.message}`)
}
const data = await response.json()
userId = data['id']
console.info('Success: user id:', userId)
} catch (error) {
console.error('Error: user id:', error)
}

Select your Team

If you have multiple teams, make sure you choose the right team by index. In this example, we just select the first team.

let teamId

try {
const response = await fetch(process.env.HELIO_RENDER_API_URL + `/users/${userId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})
if (!response.ok) {
const error = await response.json()
throw new Error(`HTTP error! status: ${response.status}: ${error.message}`)
}
const data = await response.json()
teamId = data.teams[0]['id']
console.info('Success: team id:', teamId)
} catch (error) {
console.error('Error: team id:', error)
}

Create Project

Create your project within the selected team. The only required field is the name in the body.

let project

try {
const response = await fetch(process.env.HELIO_RENDER_API_URL + `/workspaces`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
'Team-Id': teamId,
},
body: JSON.stringify({
name: 'My Project',
}),
})
if (!response.ok) {
const error = await response.json()
throw new Error(`HTTP error! status: ${response.status}: ${error.message}`)
}
project = await response.json()
console.info('Success: new project:', project)
} catch (error) {
console.error('Error: new project:', error)
}

Upload Project files

First, we load the environment variables that are required to run Rclone.

let projectUploadRcloneEnvVars

try {
const response = await fetch(
process.env.HELIO_RENDER_API_URL + `/storage/${project.storageId}/credential`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
accessMode: 'ReadWrite',
type: 'rclone',
}),
}
)
if (!response.ok) {
const error = await response.json()
throw new Error(`HTTP error! status: ${response.status}: ${error.message}`)
}
const data = await response.json()
projectUploadRcloneEnvVars = data.config
console.info('Success: project rclone storage config: ', projectUploadRcloneEnvVars)
} catch (error) {
console.error('Error: project rclone storage config:', error)
}

Then we run Rclone to upload the files.

try {
const command = `${process.env.RCLONE_PATH} sync ${process.env.PROJECT_PATH} ${projectUploadRcloneEnvVars['RCLONE_CONFIG_ENDPOINT']}`
await exec(command, { env: projectUploadRcloneEnvVars, stdio: 'inherit' })
console.info('Success: uploaded')
} catch (error) {
console.error('Error: uploading:', error)
}

Start a Rendering

Each render engine has it's own required fields which you can find a detailed description inside our Render API reference in the POST /renders section.

let render

try {
const response = await fetch(process.env.HELIO_RENDER_API_URL + `/renders`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
'Team-Id': teamId,
},
body: JSON.stringify({
projectName: project.name,
file: process.env.PROJECT_FILE,
workspaceId: project.id,
engine: 'blender_4_0_cycles',
}),
})
if (!response.ok) {
const error = await response.json()
throw new Error(`HTTP error! status: ${response.status}: ${error.message}`)
}
render = await response.json()
console.info('Success: start rendering', render)
} catch (error) {
console.error(error.message)
console.error(error.data)
console.error('Error: start rendering:', error)
}


If you want to do a mass submit of like 10+ jobs with one API call, you use this code:

let renders

try {
const response = await fetch(process.env.HELIO_RENDER_API_URL + `/renders`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
'Team-Id': teamId,
},
body: JSON.stringify([
{
projectName: project.name,
file: process.env.PROJECT_FILE,
workspaceId: project.id,
engine: 'blender_4_0_cycles',
},
{
projectName: project.name,
file: process.env.PROJECT_FILE,
workspaceId: project.id,
engine: 'blender_4_0_cycles',
},
]),
})
if (!response.ok) {
const error = await response.json()
throw new Error(`HTTP error! status: ${response.status}: ${error.message}`)
}
renders = await response.json()
console.info('Success: start rendering', render)
} catch (error) {
console.error(error.message)
console.error(error.data)
console.error('Error: start rendering:', error)
}

Be aware that if you use mass submit, you're using a new variable called renders, which is an array of render objects. In the following sections, we still depend on a variable called render which is a single render object.

Render Status Check

To know when a rendering is done, you'll have to poll the render detail endpoint every few minutes.

try {
const response = await fetch(process.env.HELIO_RENDER_API_URL + `/renders/${render.name}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
'Team-Id': teamId,
},
})
if (!response.ok) {
const error = await response.json()
throw new Error(`HTTP error! status: ${response.status}: ${error.message}`)
}
render = await response.json()
console.info('Success: status rendering:', render)
} catch (error) {
console.error('Error: status rendering:', error)
}

Download Render Results

A rendering has multiple storages, an input and an output. to be able to download the render result files, we would need to get the output storage ID first.

let renderOutputStorageId

try {
const response = await fetch(
process.env.HELIO_RENDER_API_URL + `/renders/${render.name}/storage`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
'Team-Id': teamId,
},
}
)
if (!response.ok) {
const error = await response.json()
throw new Error(`HTTP error! status: ${response.status}: ${error.message}`)
}
const data = await response.json()
renderOutputStorageId = data['outputStorageId']
console.info('Success: rendering storages:', renderOutputStorageId)
} catch (error) {
console.error('Error: rendering storages:', error)
}

Then we can load the environment variables that are required to run Rclone.

et renderDownloadRcloneEnvVars

try {
const response = await fetch(
process.env.HELIO_RENDER_API_URL + `/storage/${renderOutputStorageId}/credential`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
accessMode: 'ReadWrite',
type: 'rclone',
}),
}
)
if (!response.ok) {
const error = await response.json()
throw new Error(`HTTP error! status: ${response.status}: ${error.message}`)
}
const data = await response.json()
renderDownloadRcloneEnvVars = data.config
console.info('Success: results storage config: ', renderDownloadRcloneEnvVars)
} catch (error) {
console.error('Error: results storage config:', error)
}

Then we run Rclone to download the render result files.

try {
await fs.promises.mkdir(process.env.RENDER_RESULT_PATH, { recursive: true })
const command = `${process.env.RCLONE_PATH} copy ${renderDownloadRcloneEnvVars['RCLONE_CONFIG_ENDPOINT']} ${process.env.RENDER_RESULT_PATH}`
await exec(command, { env: renderDownloadRcloneEnvVars, stdio: 'inherit' })
console.info('Success: download completed.')
} catch (error) {
console.error('Error: download:', error)
}

Did this answer your question?