Tasks
Run, stream, and continue tasks against a Handinger worker.
A task is one run of a worker. Each task has a chronological list of turns — one per user message and the agent’s reply. Tasks store the full conversation, attached files, sources, and usage metrics, so you can return to them later or hand them off across services.
This guide covers running new tasks, continuing existing ones, streaming live output, and the supporting operations (retrieve, archive).
Create a task
Section titled “Create a task”The minimum required parameters are workerId (the template to run) and input (the
user message). The call blocks until the worker finishes and returns a Worker
object reflecting the final state.
import Handinger from '@ramensoft/handinger';
const client = new Handinger({ apiKey: process.env['HANDINGER_API_KEY'], // This is the default and can be omitted});
const worker = await client.tasks.create({ input: "What's the weather today in Barcelona?", workerId: 'wrk_vk81XUHKHG-qr4',});
console.log(worker.id);import osfrom handinger import Handinger
client = Handinger( api_key=os.environ.get("HANDINGER_API_KEY"), # This is the default and can be omitted)worker = client.tasks.create( input="What's the weather today in Barcelona?", worker_id="wrk_vk81XUHKHG-qr4",)print(worker.id)package main
import ( "context" "fmt"
"github.com/ramensoft/handinger-go" "github.com/ramensoft/handinger-go/option")
func main() { client := handinger.NewClient( option.WithAPIKey("My API Key"), ) worker, err := client.Tasks.New(context.TODO(), handinger.TaskNewParams{ CreateTask: handinger.CreateTaskParam{ Input: "What's the weather today in Barcelona?", }, }) if err != nil { panic(err.Error()) } fmt.Printf("%+v\n", worker.ID)}require "handinger"
handinger = Handinger::Client.new(api_key: "My API Key")
worker = handinger.tasks.create(input: "What's the weather today in Barcelona?")
puts(worker)handinger tasks create \ --api-key 'My API Key' \ --input "What's the weather today in Barcelona?"curl https://handinger.com/api/tasks \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $HANDINGER_API_KEY" \ -d "{ \"input\": \"What's the weather today in Barcelona?\", \"budget\": \"standard\", \"taskId\": \"tsk_2Z-YWz3hFq6VlW\", \"workerId\": \"wrk_vk81XUHKHG-qr4\" }"Budget
Section titled “Budget”The budget parameter controls how aggressively the worker spends to complete the
task. Higher budgets unlock more tool calls and longer reasoning, but cost more
credits.
| Budget | When to use it |
|---|---|
low | Quick lookups and classification — typically a single tool call. |
standard | Default. Balanced research with a handful of tool calls. |
high | Multi-step research, comparison, or extraction across many sources. |
unlimited | Long-running, exhaustive jobs. Use with care — there is no spend cap. |
Pass budget: 'high' (or the level you want) alongside workerId and input in
the call shown above.
Continue a task
Section titled “Continue a task”Reuse a prior taskId to add a follow-up turn instead of starting a new task. The
worker keeps full conversation history, any attached files, and the cached state of
external tools.
There is no separate “continue” endpoint — POST /api/tasks is the single entry
point. Adding taskId to the request above is what tells Handinger to append a
turn rather than start a fresh task.
Bring-your-own task id
Section titled “Bring-your-own task id”You can also send a taskId for a task that does not exist yet — Handinger will
create the task with that id. This is the recommended pattern when you want to
correlate Handinger tasks with rows in your own database without a second
round-trip. Generate a ULID (or any unique string) on your side, insert your own
row, and pass the same id as taskId when you call tasks.create.
Stream the response
Section titled “Stream the response”For long-running tasks, prefer streaming so your UI can render token-by-token. The API returns Server-Sent Events when either:
- You send
stream: truein the request body, or - You set the
Acceptheader totext/event-stream.
Each event is a structured chunk of the agent’s output (text deltas, tool calls,
source citations, file outputs, and a final done marker). Each SDK exposes the
stream idiomatically (async iterators in TypeScript and Python, channels in Go,
etc.) — see the SDK reference for the exact iteration pattern.
Attach files
Section titled “Attach files”Send the request as multipart/form-data to attach files. Up to 5 files per
request, 10 MB per file, and 30 MB total. The bytes are bootstrapped into
the worker’s workspace before the task starts and remain available for follow-up
turns.
Each SDK accepts file inputs in its native form (e.g. ReadStream in TypeScript,
file objects in Python, io.Reader in Go); the SDK switches the request to
multipart/form-data automatically when files are present.
Retrieve a task
Section titled “Retrieve a task”GET /api/tasks/{taskId} returns the task metadata plus every turn the worker has
recorded — useful for backfilling history into your own UI, or for asynchronously
collecting the result of a fire-and-forget task.
import Handinger from '@ramensoft/handinger';
const client = new Handinger({ apiKey: process.env['HANDINGER_API_KEY'], // This is the default and can be omitted});
const taskWithTurns = await client.tasks.retrieve('tsk_01HZY31W2SZJ8MJ2FQTR3M1K9D');
console.log(taskWithTurns.task);import osfrom handinger import Handinger
client = Handinger( api_key=os.environ.get("HANDINGER_API_KEY"), # This is the default and can be omitted)task_with_turns = client.tasks.retrieve( "tsk_01HZY31W2SZJ8MJ2FQTR3M1K9D",)print(task_with_turns.task)package main
import ( "context" "fmt"
"github.com/ramensoft/handinger-go" "github.com/ramensoft/handinger-go/option")
func main() { client := handinger.NewClient( option.WithAPIKey("My API Key"), ) taskWithTurns, err := client.Tasks.Get(context.TODO(), "tsk_01HZY31W2SZJ8MJ2FQTR3M1K9D") if err != nil { panic(err.Error()) } fmt.Printf("%+v\n", taskWithTurns.Task)}require "handinger"
handinger = Handinger::Client.new(api_key: "My API Key")
task_with_turns = handinger.tasks.retrieve("tsk_01HZY31W2SZJ8MJ2FQTR3M1K9D")
puts(task_with_turns)handinger tasks retrieve \ --api-key 'My API Key' \ --task-id tsk_01HZY31W2SZJ8MJ2FQTR3M1K9Dcurl https://handinger.com/api/tasks/$TASK_ID \ -H "Authorization: Bearer $HANDINGER_API_KEY"Archive a task
Section titled “Archive a task”DELETE /api/tasks/{taskId} archives a task so it stops appearing in GET /api/tasks
listings. Turns and files are retained for audit purposes — archiving is a soft delete
that hides the task from default views. Only the worker creator can archive a
task.
import Handinger from '@ramensoft/handinger';
const client = new Handinger({ apiKey: process.env['HANDINGER_API_KEY'], // This is the default and can be omitted});
const deleteTaskResponse = await client.tasks.delete('tsk_01HZY31W2SZJ8MJ2FQTR3M1K9D');
console.log(deleteTaskResponse.archived);import osfrom handinger import Handinger
client = Handinger( api_key=os.environ.get("HANDINGER_API_KEY"), # This is the default and can be omitted)delete_task_response = client.tasks.delete( "tsk_01HZY31W2SZJ8MJ2FQTR3M1K9D",)print(delete_task_response.archived)package main
import ( "context" "fmt"
"github.com/ramensoft/handinger-go" "github.com/ramensoft/handinger-go/option")
func main() { client := handinger.NewClient( option.WithAPIKey("My API Key"), ) deleteTaskResponse, err := client.Tasks.Delete(context.TODO(), "tsk_01HZY31W2SZJ8MJ2FQTR3M1K9D") if err != nil { panic(err.Error()) } fmt.Printf("%+v\n", deleteTaskResponse.Archived)}require "handinger"
handinger = Handinger::Client.new(api_key: "My API Key")
delete_task_response = handinger.tasks.delete("tsk_01HZY31W2SZJ8MJ2FQTR3M1K9D")
puts(delete_task_response)handinger tasks delete \ --api-key 'My API Key' \ --task-id tsk_01HZY31W2SZJ8MJ2FQTR3M1K9Dcurl https://handinger.com/api/tasks/$TASK_ID \ -X DELETE \ -H "Authorization: Bearer $HANDINGER_API_KEY"Status and triggers
Section titled “Status and triggers”Every task carries a status and a triggeredBy value:
| Field | Values | Notes |
|---|---|---|
status | pending, running, completed, error, aborted | Non-streaming responses only return after the task is completed, error, or aborted. |
triggeredBy | api, ui, email, schedule | API key calls are logged as api; web dashboard usage is ui. Use this to filter audit logs. |
Common pitfalls
Section titled “Common pitfalls”402 Payment Required
Your organization is out of billing credits. Top up in the dashboard or downgrade
the task budget.
403 Forbidden on a
A task belongs to exactly one worker. Make sure the taskId from another workerworkerId you send matches
the worker the original task was created against, or omit taskId to start fresh.
The stream stops without a
Most likely a network proxy is buffering the SSE response. Pass
done eventCache-Control: no-cache and disable HTTP/1.1 keep-alive buffering on your proxy.
Use curl -N for raw debugging.