--- title: Tasks | Handinger description: 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). Every request on this page needs an API key — see [authentication](/guides/authentication/index.md) for setup. Tasks consume billing credits, so make sure your organization has a non-zero balance before sending production traffic. ## 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. - [ TypeScript](#tab-panel-14) - [ Python](#tab-panel-15) - [ Go](#tab-panel-16) - [ Ruby](#tab-panel-17) - [ CLI](#tab-panel-18) - [ cURL](#tab-panel-19) ``` 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 os from 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) ``` Terminal window ``` handinger tasks create \ --api-key 'My API Key' \ --input "What's the weather today in Barcelona?" ``` Terminal window ``` 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 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 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 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`. Client-provided ids must be unique within your organization and 1–255 characters long. Reusing an id appends a new turn; it does not overwrite the original task. ## Stream the response For long-running tasks, prefer streaming so your UI can render token-by-token. The API returns [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) when either: - You send `stream: true` in the request body, **or** - You set the `Accept` header to `text/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 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 `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. - [ TypeScript](#tab-panel-20) - [ Python](#tab-panel-21) - [ Go](#tab-panel-22) - [ Ruby](#tab-panel-23) - [ CLI](#tab-panel-24) - [ cURL](#tab-panel-25) ``` 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 os from 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) ``` Terminal window ``` handinger tasks retrieve \ --api-key 'My API Key' \ --task-id tsk_01HZY31W2SZJ8MJ2FQTR3M1K9D ``` Terminal window ``` curl https://handinger.com/api/tasks/$TASK_ID \ -H "Authorization: Bearer $HANDINGER_API_KEY" ``` ## 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. - [ TypeScript](#tab-panel-26) - [ Python](#tab-panel-27) - [ Go](#tab-panel-28) - [ Ruby](#tab-panel-29) - [ CLI](#tab-panel-30) - [ cURL](#tab-panel-31) ``` 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 os from 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) ``` Terminal window ``` handinger tasks delete \ --api-key 'My API Key' \ --task-id tsk_01HZY31W2SZJ8MJ2FQTR3M1K9D ``` Terminal window ``` curl https://handinger.com/api/tasks/$TASK_ID \ -X DELETE \ -H "Authorization: Bearer $HANDINGER_API_KEY" ``` ## 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 402 Payment Required Your organization is out of billing credits. Top up in the dashboard or downgrade the task `budget`. 403 Forbidden on a `taskId` from another worker A task belongs to exactly one worker. Make sure the `workerId` you send matches the worker the original task was created against, or omit `taskId` to start fresh. The stream stops without a `done` event Most likely a network proxy is buffering the SSE response. Pass `Cache-Control: no-cache` and disable HTTP/1.1 keep-alive buffering on your proxy. Use `curl -N` for raw debugging.