Context API
Every service handler receives a context object (ctx) that provides methods to access task data and control execution flow.
Data Access
Section titled “Data Access”ctx.input
Section titled “ctx.input”The input data for the current message. This is the payload that was routed to this service node based on the task template graph.
createService('1.0.0', (ctx) => { const input = ctx.input // input is the data routed to this service node})def handler(ctx): input = ctx.input # input is the data routed to this service nodectx.configuration
Section titled “ctx.configuration”The static configuration set on the service node in the task template editor.
createService('1.0.0', (ctx) => { const config = ctx.configuration // config is the JSON set in the UI for this service node})def handler(ctx): config = ctx.configuration # config is the dict set in the UI for this service nodectx.taskId
Section titled “ctx.taskId”The unique identifier of the current task execution.
ctx.debug
Section titled “ctx.debug”A logger that sends messages to the Requence UI in real time. Available methods: log, info, warn, error.
createService('1.0.0', (ctx) => { ctx.debug.log('Processing started') ctx.debug.warn('Something looks off')})def handler(ctx): ctx.debug.log("Processing started") ctx.debug.warn("Something looks off")Flow Control
Section titled “Flow Control”ctx.retry(delay?)
Section titled “ctx.retry(delay?)”Instructs Requence to retry this service after an optional delay in milliseconds (minimum 100 ms). No code executes after this call.
createService('1.0.0', async (ctx) => { const db = await getDbConnection()
if (!db.isConnected) { ctx.retry(2000) // retry in 2 seconds }
return db.query('SELECT ...')})def handler(ctx): db = get_db_connection()
if not db.is_connected: ctx.retry(2000) # retry in 2 seconds
return db.query("SELECT ...")ctx.abort(reason?)
Section titled “ctx.abort(reason?)”Instructs Requence to abort this service immediately. If the on fail output is not connected, the entire task will fail.
createService('1.0.0', (ctx) => { if (!ctx.input.requiredField) { ctx.abort('Missing required field') }
return processData(ctx.input)})def handler(ctx): if not ctx.input.get("requiredField"): ctx.abort("Missing required field")
return process_data(ctx.input)ctx.toOutput(name, value)
Section titled “ctx.toOutput(name, value)”Routes execution to a specific named output on the service node. This is used when your service has multiple outputs defined in the service definition.
createService('1.0.0', (ctx) => { if (ctx.input.type === 'pdf') { return ctx.toOutput('pdf', { url: '...' }) }
return ctx.toOutput('other', { raw: ctx.input })})def handler(ctx): if ctx.input.get("type") == "pdf": return ctx.to_output("pdf", {"url": "..."})
return ctx.to_output("other", {"raw": ctx.input})ctx.defer(reason?)
Section titled “ctx.defer(reason?)”Marks the current message as deferred. The service acknowledges the message but signals that the work will be completed later via the act() API. This is useful for long-running processes or external callbacks.
createService('1.0.0', (ctx) => { const messageKey = ctx.defer('waiting for external process') // Store messageKey to use later with service.act()})def handler(ctx): message_key = ctx.defer("waiting for external process") # Store message_key to use later with service.act()ctx.terminated
Section titled “ctx.terminated”A Promise that resolves when the task is stopped — either because it was cancelled via the UI or API, or because another node triggered an exit. For continuous service generators, this is how you know the service should stop producing values.
ctx.terminated also exposes a .signal property — a native AbortSignal that fires at the same moment. Pass it directly to any abort-aware API (such as fetch, node:timers/promises setTimeout, or database clients) so that a pending await inside your generator exits immediately when the task is stopped, rather than waiting for the operation to complete naturally.
import { setTimeout } from 'node:timers/promises'
// Poll every 5 seconds, but exit immediately when the task is stoppedcreateService('1.0.0', async function* (ctx) { while (true) { await setTimeout(5_000, null, { signal: ctx.terminated.signal }) yield await pollForUpdates() }})You can also race ctx.terminated as a plain Promise when the API you are calling does not support AbortSignal:
const result = await Promise.race([ fetchData(), ctx.terminated, // resolves with the stop reason when the task is cancelled])