Workers
Create, update, and delete reusable Handinger worker templates.
A worker is a reusable agent template. Its title, system instructions, output schema, attached files, MCP connections, and members live on the worker — every task inherits them. Most production integrations create a small number of workers up front and run thousands of tasks against them.
This guide covers the worker lifecycle: POST create, PATCH update, and DELETE delete. See tasks for how to actually run them.
Create a worker
Section titled “Create a worker”There are two ways to specify what the worker should do:
prompt— a natural-language description. Handinger expands it into structured instructions and, when the request implies extraction, an output schema. Use this to bootstrap a worker quickly.instructions— a fully-specified system prompt. Use this once you know exactly what behaviour you want. When you passinstructions, Handinger does not modify them.
From a prompt
Section titled “From a prompt”import Handinger from '@ramensoft/handinger';
const client = new Handinger({ apiKey: process.env['HANDINGER_API_KEY'], // This is the default and can be omitted});
const workerTemplate = await client.workers.create({ prompt: 'A worker that fact-checks short claims and returns a verdict with citations.',});
console.log(workerTemplate.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_template = client.workers.create( prompt="A worker that fact-checks short claims and returns a verdict with citations.",)print(worker_template.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"), ) workerTemplate, err := client.Workers.New(context.TODO(), handinger.WorkerNewParams{ CreateWorker: handinger.CreateWorkerParam{
}, }) if err != nil { panic(err.Error()) } fmt.Printf("%+v\n", workerTemplate.ID)}require "handinger"
handinger = Handinger::Client.new(api_key: "My API Key")
worker_template = handinger.workers.create
puts(worker_template)handinger workers create \ --api-key 'My API Key'curl https://handinger.com/api/workers \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $HANDINGER_API_KEY" \ -d "{ \"instructions\": \"You are a brand voice analyzer. Read the input text and report whether it matches Acme's playful, plain-spoken house style. Quote specific phrases.\", \"outputSchema\": { \"type\": \"bar\", \"required\": \"bar\", \"properties\": \"bar\" }, \"prompt\": \"A worker that fact-checks short claims and returns a verdict with citations.\", \"summary\": \"Audits copy against the Acme brand voice guide.\", \"title\": \"Brand voice analyzer\", \"visibility\": \"public\" }"With explicit instructions and an output schema
Section titled “With explicit instructions and an output schema”Pass instructions instead of prompt to skip the AI expansion step. When you
want deterministic structured output, also pass JSON Schema Draft-07 in
outputSchema — every task response will then expose a typed structuredOutput
object that has been validated against the schema.
A minimal outputSchema looks like this:
{ "type": "object", "required": ["verdict", "summary"], "properties": { "verdict": { "type": "string", "enum": ["supported", "debunked", "mixed", "unknown"] }, "summary": { "type": "string" }, "citations": { "type": "array", "items": { "type": "string" } } }}Visibility and members
Section titled “Visibility and members”public(default) — every member of the organization can list and run the worker.private— only invited members and the creator can see or run it.
You can flip visibility at any time with update. To invite a
specific user to a private worker, use the worker members endpoint
(POST /api/workers/{workerId}/members).
Create-time defaults
Section titled “Create-time defaults”What gets filled in automatically?
title— when omitted, Handinger assigns a random dog-themed name.summary— auto-generated from yourpromptwhen you don’t provide one.avatar— a unique dog avatar is picked per organization.instructions— generated frompromptwhen you don’t pass them. The generated prompt is detailed and durable; you can read it back via retrieve.outputSchema— generated when yourpromptimplies extraction. Passnullto keep responses free-form.
Retrieve a worker
Section titled “Retrieve a worker”Use workers.retrieve(workerId) to fetch the current configuration plus the messages
of the most recent task. This is the same endpoint the dashboard uses to render a
worker’s chat view, so the response shape is task-centric (output, output_text,
messages).
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.workers.retrieve('t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM');
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.workers.retrieve( worker_id="t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM",)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.Workers.Get( context.TODO(), "t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM", handinger.WorkerGetParams{
}, ) 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.workers.retrieve("t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM")
puts(worker)handinger workers retrieve \ --api-key 'My API Key' \ --worker-id t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GMcurl https://handinger.com/api/workers/$WORKER_ID \ -H "Authorization: Bearer $HANDINGER_API_KEY"Update a worker
Section titled “Update a worker”PATCH /api/workers/{workerId} performs a partial update — only the fields you send
are changed. Subsequent tasks immediately pick up the new instructions; tasks already
running keep their original prompt.
Updating a worker requires the creator role on that worker.
import Handinger from '@ramensoft/handinger';
const client = new Handinger({ apiKey: process.env['HANDINGER_API_KEY'], // This is the default and can be omitted});
const workerTemplate = await client.workers.update('t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM', { title: 'Claim verdict v2',});
console.log(workerTemplate.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_template = client.workers.update( worker_id="t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM", title="Claim verdict v2",)print(worker_template.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"), ) workerTemplate, err := client.Workers.Update( context.TODO(), "t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM", handinger.WorkerUpdateParams{ UpdateWorker: handinger.UpdateWorkerParam{
}, }, ) if err != nil { panic(err.Error()) } fmt.Printf("%+v\n", workerTemplate.ID)}require "handinger"
handinger = Handinger::Client.new(api_key: "My API Key")
worker_template = handinger.workers.update("t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM")
puts(worker_template)handinger workers update \ --api-key 'My API Key' \ --worker-id t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GMcurl https://handinger.com/api/workers/$WORKER_ID \ -X PATCH \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $HANDINGER_API_KEY" \ -d "{ \"instructions\": \"You are a brand voice analyzer. Read the input text and report whether it matches Acme's playful, plain-spoken house style. Quote specific phrases.\", \"outputSchema\": { \"type\": \"bar\", \"required\": \"bar\", \"properties\": \"bar\" }, \"summary\": \"Audits copy against the Acme brand voice guide.\", \"title\": \"Claim verdict v2\", \"visibility\": \"private\" }"Clearing the output schema
Section titled “Clearing the output schema”To return a worker to free-form text responses, pass outputSchema: null explicitly
— this is the one field where null and “omitted” mean different things.
await handinger.workers.update('wrk_vk81XUHKHG-qr4', { outputSchema: null });Delete a worker
Section titled “Delete a worker”DELETE /api/workers/{workerId} permanently removes the worker template and all
of its tasks, turns, files, schedules, and MCP connections. The operation is not
reversible.
Only the creator of the worker may delete it. The endpoint always returns
{ "deleted": true } once the cascade completes.
import Handinger from '@ramensoft/handinger';
const client = new Handinger({ apiKey: process.env['HANDINGER_API_KEY'], // This is the default and can be omitted});
const deleteWorkerResponse = await client.workers.delete('t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM');
console.log(deleteWorkerResponse.deleted);import osfrom handinger import Handinger
client = Handinger( api_key=os.environ.get("HANDINGER_API_KEY"), # This is the default and can be omitted)delete_worker_response = client.workers.delete( "t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM",)print(delete_worker_response.deleted)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"), ) deleteWorkerResponse, err := client.Workers.Delete(context.TODO(), "t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM") if err != nil { panic(err.Error()) } fmt.Printf("%+v\n", deleteWorkerResponse.Deleted)}require "handinger"
handinger = Handinger::Client.new(api_key: "My API Key")
delete_worker_response = handinger.workers.delete("t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GM")
puts(delete_worker_response)handinger workers delete \ --api-key 'My API Key' \ --worker-id t_org_123_w_01HZY2ZJQ8G7K42W2D7WF6V4GMcurl https://handinger.com/api/workers/$WORKER_ID \ -X DELETE \ -H "Authorization: Bearer $HANDINGER_API_KEY"Common pitfalls
Section titled “Common pitfalls”403 Forbidden when updating or deleting
Update and delete require the creator role. If you’re using an API key that
was issued by a different user, transfer ownership in the dashboard first, or
have the original creator run the call.
The worker keeps generating with the old instructions
In-flight tasks finish with the prompt they started with. Wait for them to
complete (or cancel them via the dashboard) before sending a new task.
Output schema validation fails
The schema must be valid JSON Schema Draft-07. Top-level type: "object" is
required, and unknown keywords are rejected at create/update time, not at task
time. Use json-schema.org’s validators to
debug.