The Tela SDK for JavaScript provides a simple and powerful way to interact with the Tela API. This SDK allows you to create chat completions, handle file uploads, and manage various resources with ease.
You can install the Tela SDK via npm, yarn, or pnpm:
npm install @meistrari/tela-sdk-js
yarn add @meistrari/tela-sdk-js
pnpm add @meistrari/tela-sdk-js
First, you need to import the SDK and initialize it with your API key:
import { TelaSDK } from '@meistrari/tela-sdk-js'
const tela = new TelaSDK({ apiKey: 'your-api-key' })
The Canvas API provides a type-safe way to execute prompts with schema validation and multiple execution modes.
Execute a canvas and wait for the complete result:
import { TelaSDK } from '@meistrari/tela-sdk-js'
const tela = new TelaSDK({
apiKey: 'your-api-key'
})
// Get canvas with input/output schemas
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
query: schema.string()
}),
output: schema => schema.object({
result: schema.string()
}),
})
// Execute synchronously - direct result access
const result = await canvas.execute({ query: 'What is the capital of France?' }).result
console.log(result) // { result: "Paris" }
// Alternative: get execution object for more control
const execution = await canvas.execute({ query: 'What is the capital of France?' })
const result2 = await execution.result
For long-running operations, use async mode with automatic polling:
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
document: schema.file(),
query: schema.string()
}),
output: schema => schema.object({
summary: schema.string()
}),
})
// Execute asynchronously with polling - direct result access
const result = await canvas.execute(
{
document: TelaFile.create(documentBlob, { name: 'report.pdf' }),
query: 'Summarize this document'
},
{
async: true,
pollingInterval: 1000, // Poll every 1 second
pollingTimeout: 60000, // Timeout after 60 seconds
}
).result
console.log(result.summary)
Stream results as they're generated:
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
prompt: schema.string()
}),
output: schema => schema.object({
response: schema.string()
}),
})
// Execute with streaming - direct iteration
for await (const chunk of canvas.execute(
{ prompt: 'Write a story about a robot' },
{ stream: true }
).result) {
process.stdout.write(chunk.response || '')
}
Monitor execution lifecycle with events. Events are only useful with async executions - synchronous and streaming executions complete too quickly for event-driven monitoring to be practical.
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
query: schema.string()
}),
output: schema => schema.object({
result: schema.string()
}),
})
// Start async execution to attach event listeners
const execution = await canvas.execute(
{ query: 'What is the capital of France?' },
{
async: true,
pollingInterval: 1000,
pollingTimeout: 60000,
}
)
// Access current status at any time
console.log(execution.status) // 'created'
// Listen for status changes
execution.on('statusChange', (status) => {
console.log(`Status: ${status}`) // created → running → succeeded
})
// Listen for polling updates
execution.on('poll', (pollResult) => {
console.log(`Poll - Status: ${pollResult.status}, Request ID: ${pollResult.requestId}`)
})
// Listen for successful completion
execution.on('success', (result) => {
console.log('Success!', result)
console.log('Request ID:', result.requestId)
})
// Listen for errors (prevents throwing)
execution.on('error', (error) => {
console.error('Failed:', error.message)
// With error listener: error event is emitted, promise resolves to undefined
})
// Start event-driven polling (non-blocking)
execution.poll()
// Events will fire as polling progresses
// The result will be available in the success event callback
// Continue with other work while polling happens in the background...
Why Async Only?
Events are designed for tracking progress over time. Synchronous executions complete immediately (blocking until done), so by the time you can access the execution object, all events have already fired. Streaming executions provide their own iteration mechanism via async generators, making events redundant.
Status Property:
The status property provides the current execution state:
created → running → succeeded or failedStatus is set to succeeded only after successful validation. If validation fails, status will be failed even if the API request succeeded.
Available Events (Async Executions Only):
success - Emitted when execution completes successfully (after validation)error - Emitted on failure (if listeners exist, errors won't throw)statusChange - Emitted on status transitionspoll - Emitted during polling (initial response + each poll)Error Handling:
undefinedImportant: You must call execution.poll() or await execution.result to start polling - events won't fire until polling begins.
See Event Examples for detailed usage patterns.
Every API request to Tela returns a unique requestId (from the x-request-id header) that can be used for debugging, tracking, and correlating with server logs. The SDK exposes request IDs in three ways:
const canvas = await tela.canvas.get({ id: 'canvas-id' })
// Start an async execution
const execution = await canvas.execute(
{ query: 'test' },
{ async: true }
)
// Method 1: Access directly from execution object
// This is the request ID from the initial request that created the execution
const requestId = await execution.requestId
console.log('Request ID:', requestId)
// Method 2: Access from poll events
// Each poll event includes the request ID from that specific polling request
execution.on('poll', (pollResult) => {
console.log('Request ID from this poll:', pollResult.requestId)
})
// Method 3: Access from success event
// The success event includes the request ID from the final successful polling request
execution.on('success', (result) => {
console.log('Request ID from final request:', result.requestId)
})
// Start polling
execution.poll()
Important Notes:
execution.requestId - Request ID from the execution creation request (POST /v2/chat/completions)pollResult.requestId - Request ID from each individual polling request (GET /v2/chat/completions/:id)result.requestId - Request ID from the final successful polling requestUse Cases:
See Request ID Example for a complete demonstration.
The Canvas API validates your inputs and outputs against the server schema:
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
text: schema.string(),
file: schema.file(), // TelaFile validator
settings: schema.object({
temperature: schema.number(),
maxTokens: schema.number(),
}),
}),
output: schema => schema.object({
analysis: schema.string(),
confidence: schema.number(),
}),
skipSchemaValidation: false, // Enable schema validation warnings during canvas retrieval (default)
})
// TypeScript will ensure you provide the correct types
const result = await canvas.execute({
text: 'Analyze this document',
file: TelaFile.create(blob, { name: 'data.pdf' }),
settings: {
temperature: 0.7,
maxTokens: 1000,
}
}).result
// Skip result validation to trust API response without Zod validation
const resultWithoutValidation = await canvas.execute(
{
text: 'Analyze this document',
file: TelaFile.create(blob, { name: 'data.pdf' }),
settings: {
temperature: 0.7,
maxTokens: 1000,
}
},
{
skipResultValidation: true // Skip Zod validation on results, just typecast
}
).result
Process multiple files in a single canvas execution:
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
documents: schema.array(schema.file()),
instructions: schema.string()
}),
output: schema => schema.object({
comparison: schema.string()
}),
})
const result = await canvas.execute({
documents: [
TelaFile.create('https://example.com/doc1.pdf', { range: [0, 5] }),
TelaFile.create('https://example.com/doc2.pdf', { range: [0, 5] }),
TelaFile.create(localFileBlob, { name: 'doc3.pdf' })
],
instructions: 'Compare these documents and identify key differences'
}).result
console.log(result.comparison)
Receive completion notifications via webhook for async executions:
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
document: schema.file(),
query: schema.string()
}),
output: schema => schema.object({
analysis: schema.string()
}),
})
// Execute with webhook notification - you can still poll if needed
const result = await canvas.execute(
{
document: TelaFile.create('https://example.com/large-report.pdf'),
query: 'Analyze this report'
},
{
async: true,
webhookUrl: 'https://your-server.com/webhook',
pollingTimeout: 300000, // 5 minutes
}
).result
// OR wait for the webhook notification on your server instead of polling
Add tags to your canvas executions for filtering, categorization, and analytics:
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
query: schema.string()
}),
output: schema => schema.object({
result: schema.string()
}),
})
// Execute with tags (works with all execution modes)
const result = await canvas.execute(
{ query: 'Analyze customer feedback' },
{
tags: ['production', 'analytics', 'customer-feedback']
}
).result
// Tags work with async executions
const asyncResult = await canvas.execute(
{ query: 'Process large dataset' },
{
async: true,
tags: ['batch-processing', 'analytics']
}
).result
// Tags work with streaming executions
for await (const chunk of canvas.execute(
{ query: 'Generate report' },
{
stream: true,
tags: ['streaming', 'reports']
}
).result) {
process.stdout.write(chunk.result || '')
}
Use Cases:
production, staging, development)analytics, reporting, summarization)Tags are sent to the Tela API and can be used for filtering and analytics in your Tela dashboard.
Retrieve and monitor async executions by their ID, useful for job queues, resuming workflows, or multi-user dashboards:
// Start an async execution
const execution = await canvas.execute(
{ query: 'Process this data' },
{ async: true }
)
// Store the execution ID (e.g., in a database)
const executionId = execution.id
// Later, fetch the execution by ID
const fetchedExecution = await canvas.getExecution(executionId, {
pollingInterval: 2000, // Optional: custom polling interval
pollingTimeout: 120000 // Optional: custom timeout
})
console.log(fetchedExecution.status) // 'running', 'succeeded', or 'failed'
// Use event-driven polling for non-blocking progress tracking
// Note: poll() works for ANY async execution, not just fetched ones!
fetchedExecution.on('statusChange', (status) => {
console.log(`Status: ${status}`)
})
fetchedExecution.on('success', (result) => {
console.log('Completed!', result)
})
fetchedExecution.on('error', (error) => {
console.error('Failed:', error)
})
// Start polling without blocking (returns immediately)
fetchedExecution.poll()
// Continue with other work while polling runs in background...
console.log('Polling in background, doing other work...')
// Later, you can still await the result if needed
const result = await fetchedExecution.result
Alternative: Use poll() on newly started executions
You can also use event-driven polling immediately when starting an execution:
const execution = await canvas.execute(
{ query: 'Process this data' },
{ async: true }
)
// Set up event listeners
execution.on('statusChange', status => console.log('Status:', status))
execution.on('success', result => console.log('Done!', result))
execution.on('error', error => console.error('Failed:', error))
// Start non-blocking polling
execution.poll()
// Do other work...
console.log('Execution polling in background')
Key Features:
canvas.getExecution(id, options?) - Fetch execution by UUID
execution.poll() - Start event-driven polling
void (non-blocking)statusChange, poll, success, errorImportant Notes:
⚠️ Events require polling or awaiting .result: Events are only emitted when polling is active. You must either:
execution.poll() to start event-driven polling, ORexecution.result which starts polling automatically// ❌ Events won't fire - no polling started
const execution = await canvas.execute({ query: 'test' }, { async: true })
execution.on('success', result => console.log('Done')) // This will never fire!
// ✅ Correct - poll() starts event-driven polling
const execution = await canvas.execute({ query: 'test' }, { async: true })
execution.on('success', result => console.log('Done'))
execution.poll() // Now events will fire
// ✅ Also correct - awaiting result starts polling
const execution = await canvas.execute({ query: 'test' }, { async: true })
execution.on('success', result => console.log('Done'))
await execution.result // Polling starts, events fire
⚠️ Already completed executions don't emit events: If you fetch an execution that has already finished (succeeded or failed), the success and error events won't fire because there's no polling needed. Instead, check the status and access the result directly:
const execution = await canvas.getExecution(executionId)
if (execution.status === 'succeeded') {
// Access result directly - success event won't fire
const result = await execution.result
console.log('Already completed:', result)
}
else if (execution.status === 'failed') {
// Handle failure - error event won't fire
try {
await execution.result
}
catch (error) {
console.error('Already failed:', error)
}
}
else {
// Still running - events will fire when you start polling
execution.on('success', result => console.log('Completed!', result))
execution.on('error', error => console.error('Failed:', error))
execution.poll()
}
Use Cases:
See Fetch and Poll Examples for detailed usage patterns.
The TelaFile API provides flexible file handling with support for various input types:
import { TelaFile } from '@meistrari/tela-sdk-js'
// From a Blob or File
const fileFromBlob = TelaFile.create(blob, { name: 'custom.pdf' })
// From a URL
const fileFromUrl = TelaFile.create('https://example.com/document.pdf')
// From bytes with explicit MIME type
const fileFromBytes = TelaFile.create(
new Uint8Array(['...']),
{
mimeType: 'application/pdf',
name: 'document.pdf'
}
)
// From a ReadableStream with explicit MIME type
const fileFromStream = TelaFile.create(
readableStream,
{
mimeType: 'image/jpeg',
name: 'photo.jpg'
}
)
// Vault reference (for files already uploaded)
const vaultFile = TelaFile.create('vault://file-id')
// With page range for PDFs
const fileWithRange = TelaFile.create(
'https://example.com/document.pdf',
{
range: [0, 5], // Pages 0-5
parserType: 'pdf'
}
)
Version 2.0 of the Tela SDK introduces significant improvements to type safety, schema validation, and API design. This guide will help you migrate your code from v1.x to v2.
completions.create() → canvas.get() + execute()The v1 API used a single completions.create() method. V2 introduces a two-step pattern: first retrieve a canvas, then execute it.
v1.x:
const tela = new TelaSDK({ apiKey: 'your-api-key' })
const completion = await tela.completions.create({
canvasId: 'canvas-id',
variables: {
query: 'What is the capital of France?'
}
})
console.log(completion.choices[0].message.content)
v2:
const tela = new TelaSDK({ apiKey: 'your-api-key' })
// First, get the canvas
const canvas = await tela.canvas.get({
id: 'canvas-id'
})
// Then execute it
const result = await canvas.execute({
query: 'What is the capital of France?'
}).result
console.log(result)
V2 introduces optional runtime and compile-time type safety with Zod schema validation. Schemas validate data at runtime (catching invalid data) and provide TypeScript types at compile-time. You can use the SDK without schemas if you prefer.
v1.x (no schema validation):
const completion = await tela.completions.create<
{ query: string },
{ result: string }
>({
canvasId: 'canvas-id',
variables: {
query: 'Hello'
}
})
v2 (without schemas - works just like v1.x):
const canvas = await tela.canvas.get({
id: 'canvas-id'
})
const result = await canvas.execute({ query: 'Hello' }).result
// result is typed as unknown, no runtime validation
v2 (with optional schema validation for runtime + compile-time safety):
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
query: schema.string()
}),
output: schema => schema.object({
result: schema.string()
})
})
// Runtime validation ensures data matches schema
// TypeScript knows the exact types at compile-time
const result = await canvas.execute({ query: 'Hello' }).result
// result.result is a string (validated and typed)
The TelaFile constructor now requires mimeType for Uint8Array and ReadableStream inputs (for compatibility with storage solutions).
v1.x:
import { TelaFile } from '@meistrari/tela-sdk-js'
const file = new TelaFile(blob, { range: [0, 5] })
const fileFromBytes = new TelaFile(new Uint8Array([/* ... */])) // mimeType optional
v2:
import { TelaFile } from '@meistrari/tela-sdk-js'
// No change needed for Blob, File, or URL strings
const file = new TelaFile(blob, { range: [0, 5] })
// For Uint8Array or ReadableStream, mimeType is now required:
const fileFromBytes = new TelaFile(
new Uint8Array([/* ... */]),
{
mimeType: 'application/pdf',
name: 'document.pdf'
}
)
// Alternative: use tela.createFile() helper
const fileFromStream = tela.createFile(readableStream, { mimeType: 'image/jpeg' })
Streaming now returns an async generator directly via the .result property.
v1.x:
const stream = await tela.completions.create({
canvasId: 'canvas-id',
stream: true,
variables: { query: 'Tell me a story' }
})
for await (const chunk of stream) {
process.stdout.write(chunk.message.content || '')
}
v2:
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
query: schema.string()
}),
output: schema => schema.object({
response: schema.string()
})
})
const execution = canvas.execute(
{ query: 'Tell me a story' },
{ stream: true }
)
for await (const chunk of execution.result) {
process.stdout.write(chunk.response || '')
}
V2 introduces async execution with automatic polling. V1.x only supported synchronous execution (the request would block until completion).
v1.x:
// Synchronous only - blocks until the execution completes
const response = await tela.completions.create({
canvasId: 'canvas-id',
variables: { query: 'Analyze this' }
})
// This could take a very long time for long-running executions
console.log(response.choices[0].message.content)
v2:
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({ query: schema.string() }),
output: schema => schema.object({ result: schema.string() })
})
// Automatic polling
const execution = canvas.execute(
{ query: 'Analyze this' },
{
async: true,
pollingInterval: 1000, // Poll every 1 second
pollingTimeout: 60000, // Timeout after 60 seconds
webhookUrl: 'https://your-server.com/webhook' // Optional
}
)
const result = await execution.result
console.log(result.result)
The response structure has been simplified.
v1.x:
const completion = await tela.completions.create({
canvasId: 'canvas-id',
variables: { query: 'Hello' }
})
// Access via nested structure
console.log(completion.choices[0].message.content)
v2:
const canvas = await tela.canvas.get({
id: 'canvas-id',
output: schema => schema.object({
answer: schema.string()
})
})
const response = await canvas.execute({ query: 'Hello' }).result
// Direct access to structured output
console.log(response.answer)
tela.completions.create() with tela.canvas.get() + canvas.execute()schema builder function for type safetymimeType parameter when creating TelaFile from Uint8Array or ReadableStream.result property for iterationV2 introduces a comprehensive event system for monitoring execution lifecycle across all execution modes (sync, async, streaming):
const execution = await canvas.execute(
{ query: 'Analyze this' },
{ async: true }
)
// Monitor progress with events
execution.on('statusChange', (status) => {
console.log(`Status: ${status}`)
})
execution.on('poll', (pollResult) => {
console.log(`Polling - ${pollResult.status}`)
})
execution.on('success', (result) => {
console.log('Completed!', result)
})
// Graceful error handling
execution.on('error', (error) => {
console.error('Failed:', error.message)
// With listener: no throw, promise resolves to undefined
})
const result = await execution.result
Available Events:
success - Completion with validated resulterror - Failure notification (prevents throwing if listener exists)statusChange - Status transitionspoll - Polling updates (async only)See Event Examples for comprehensive patterns and use cases.
V2 introduces a flexible execution API that supports both direct result access and execution object retrieval:
// Direct result access (recommended for simple cases)
const result = await canvas.execute({ query: 'Hello' }).result
// Get execution object for more control
const execution = await canvas.execute({ query: 'Hello' })
const result2 = await execution.result
V2 validates your schemas against the server configuration and warns you about mismatches:
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
wrongField: schema.string() // Will warn if not in server schema
}),
skipSchemaValidation: false // Enable warnings (default)
})
V2 supports vault file references:
const vaultFile = new TelaFile('vault://file-id')
When you have schemas defined, you can skip runtime validation for better performance (you'll still get TypeScript types):
const canvas = await tela.canvas.get({
id: 'canvas-id',
input: schema => schema.object({
query: schema.string()
}),
output: schema => schema.object({
answer: schema.string()
})
})
const execution = canvas.execute(
{ query: 'Hello' },
{ skipResultValidation: true } // Skip runtime validation, trust API response
)
// Still typed as { answer: string }, but no validation at runtime
const result = await execution.result
V2 provides access to the raw, unprocessed API response alongside the parsed result:
const canvas = await tela.canvas.get({
id: 'canvas-id',
output: schema => schema.object({
answer: schema.string()
})
})
const execution = await canvas.execute({ query: 'Hello' })
// Get the parsed, validated result
const result = await execution.result
console.log(result.answer) // Type-safe access
// Get the raw API response
const rawResult = await execution.rawResult
console.log(rawResult) // Complete API response with all metadata
// You can await either one first - both will be available
const raw = await execution.rawResult // Triggers execution
const parsed = await execution.result // Reuses same execution
This is useful when you need:
V2 introduces the ability to fetch existing async executions by ID and monitor them with event-driven polling:
// Start an async execution
const execution = await canvas.execute(
{ query: 'Process this data' },
{ async: true }
)
// Store the execution ID (e.g., in a database)
const executionId = execution.id
// Later, fetch the execution by ID
const fetchedExecution = await canvas.getExecution(executionId, {
pollingInterval: 2000,
pollingTimeout: 120000
})
// Use event-driven polling (non-blocking)
fetchedExecution.on('statusChange', (status) => {
console.log(`Status changed: ${status}`)
})
fetchedExecution.on('success', (result) => {
console.log('Completed!', result)
})
fetchedExecution.on('error', (error) => {
console.error('Failed:', error)
})
// Start polling without blocking
fetchedExecution.poll()
// Continue with other work...
console.log('Polling in background')
This enables powerful use cases:
See Fetch and Poll Examples for detailed patterns.
V2 introduces support for tagging executions for filtering, categorization, and analytics:
// Add tags to any execution mode
const result = await canvas.execute(
{ query: 'Analyze customer feedback' },
{
tags: ['production', 'analytics', 'customer-feedback']
}
).result
// Tags work with async executions
const asyncResult = await canvas.execute(
{ query: 'Process large dataset' },
{
async: true,
tags: ['batch-processing', 'analytics']
}
).result
// Tags work with streaming executions
for await (const chunk of canvas.execute(
{ query: 'Generate report' },
{
stream: true,
tags: ['streaming', 'reports']
}
).result) {
process.stdout.write(chunk.result || '')
}
Use Cases:
production, staging, development)analytics, reporting, summarization)V2 adds support for setting workspace task titles when using deployed applications via applicationId:
const canvas = await tela.canvas.get({
applicationId: 'app-id' // Using deployed application instead of canvas ID
})
// Set the title of the workspace task
const result = await canvas.execute(
{ query: 'Process request' },
{
label: 'Customer Dashboard Query'
}
).result
When you execute a canvas using applicationId, it creates a task in the application's workspace. The label field sets the title of that workspace task, making it easier to identify and track executions in your Tela workspace.
Note: The label field is only applicable when using applicationId. A warning will be logged if you provide a label without an applicationId.
Use Cases:
If you encounter issues during migration, please: