Skip to content
Get API Key

API reference

Events

List a task's events, send it messages, answers and an accept or a send back, and follow what happens as it happens.

Updated
View as Markdown

An event is one thing that happened in a task, in order: a message, a tool the worker used, a stop. A task's events tell its whole story, and you can read them at any time.

These routes list a task's events, send it events, and stream them live. Reading the task says where it stands now. Its events say how it got there. You send events to steer it: a message, a stop, an answer to an approval, or what you think of the result.

Runs and events explains the order events come in and how to read them without missing one. Work a worker hands to a helper runs in a thread of the task. Its steps are in these events too, with a task_thread_id. The routes for threads are not on this page.

The event object

Every event has the same five fields: type, id, task_id, sequence and processed_at. The rest depend on the type, and each type is listed below with the fields it adds. Times are RFC 3339 in UTC, to the millisecond. The types fall into a few groups:

  • user.* and system.message are what was sent to the task.
  • worker.* is what the worker did: its answers, the tools it used and what they answered, and its work with helpers.
  • span.* marks the start and end of a model request or an outcome check round.
  • task.* says where the task stands: running, stopped, failed, changed or deleted.
  • workflow_run.* follows a workflow run, a plan of phases that helpers work through. Little Worker may add event types, and fields to an event, without a new API version. Skip a type you do not know instead of failing on it.

Fields

  • typestring
    What kind of event it is.
  • idevent id
    The event's id: event_ and 32 hex digits. A worker.tool_use event's id is also the tool use id that its answers name.
  • task_idtask id
    The task it belongs to.
  • sequencestring
    Its place in the task's chat, a whole number as a string, such as "10". It orders lists, streams and every cursor, since two events can share a time. The sequence numbers of one task's events skip, because the chat holds more than the task's events.
  • processed_attime or null
    When Little Worker wrote the event. A user.message sent while the worker is busy has null until a run takes it, and stays listed where it was accepted.
  • user.messageevent
    A message sent to the task, by a person in the app or by you with a key.
    Show its 2 fields
    • message_idmessage id
      The message's id: message_ and 32 hex digits.
    • contentarray of content blocks
      What was sent, as it was sent: text, image and document blocks. A redacted block is content that is withheld.
  • system.messageevent
    A system message sent right after a person's message. It is listed right after that message, with the same sequence.
    Show its 1 fields
    • contentarray of text blocks
      The text the model reads in its system role.
  • user.define_outcomeevent
    An outcome set for the task: what its work is checked against. Listed where it was sent.
    Show its 5 fields
    • outcome_idoutcome id
      The outcome, outc_ and 32 hex digits.
    • descriptionstring
      What done looks like.
    • rubricobject
      The check, as { "type": "text", "content" } or { "type": "file", "file_id" }.
    • done_meansarray of strings or null
      A short list of checks graded one by one, sent in place of a rubric. null when the outcome has a rubric.
    • max_iterationsinteger
      How many check rounds before the outcome gives up.
  • user.approvalevent
    An approval was answered, by a key or by the person in the app.
    Show its 4 fields
    • tool_use_idevent id
      The tool use that asked.
    • resultstring
      The answer.

      One of allow, deny

    • deny_messagestring or null
      Why it was denied, as the worker reads it.
    • api_key_idAPI key id or null
      The key that answered. null when the person answered in the app.
  • user.interruptevent
    The work was stopped, by your user.interrupt or by the person's Stop.
    Show its 1 fields
    • run_idrun id
      The run it stopped.
  • user.tool_resultevent
    A tool's answer, sent back by a Mac that ran it. A Mac is the person's own computer, so its answers are listed as the person's.
    Show its 5 fields
    • tool_use_idevent id
      The tool use it answers.
    • statusstring
      How the tool ended. outcome_unknown is a started call that was stopped, when nobody can prove what it did.

      One of succeeded, failed, cancelled, outcome_unknown

    • outputJSON
      What the tool answered. Left out when it answered nothing.
    • errorobject
      Why the tool failed. Left out when it did not.
      Show its 2 fields
      • codestring
        The reason, such as approval_expired.
      • messagestring
        What went wrong.
    • is_errorboolean
      true when the tool failed.
  • user.custom_tool_resultevent
    Your answer to a custom tool use.
    Show its 3 fields
    • custom_tool_use_idevent id
      The worker.custom_tool_use it answers.
    • contentarray of content blocks
      The answer: text, image, document and search_result blocks.
    • is_errorboolean
      true when it is a failure the worker should read as one.
  • worker.messageevent
    The worker's answer. It holds the whole text, and it is the record of what the worker said.
    Show its 3 fields
    • message_idmessage id
      The message's id.
    • contentarray of content blocks
      The answer, as text blocks. A redacted block is content that is withheld.
    • sourcesarray of objects
      The web pages the answer cites, each once, in the order it cites them: a url and a title. Empty when it cites none.
  • worker.tool_useevent
    The worker used a tool of its own. Any password in the input is masked.
    Show its 6 fields
    • idevent id
      The tool use id. The tool's result, an approval and a requires_action stop all name the step by it.
    • namestring
      The tool's name.
    • inputJSON
      What the worker sent it.
    • evaluated_permissionstring
      What decided the step: allow, ask or deny. Left out while nothing has decided it yet.

      One of allow, ask, deny

    • evaluationobject
      Why. Left out while nothing has decided the step, and on a step refused before any setting applied. Treat a type or a reason_code you do not know as a plain ask.
      Show its 2 fields
      • typestring
        always_allow and always_ask are the tool's own setting. auto is the automatic check. fixed_rule is an action that always asks, whatever the setting says.

        One of always_allow, always_ask, auto, fixed_rule

      • evaluated_permissionobject
        On auto and fixed_rule only: the decision. Its type is allow, ask or deny. A reason_code goes with ask and deny: on auto, high_risk for a block and indeterminate when the check was unsure, failed or ran out of time; on fixed_rule, one of send, pay, post, delete, change_settings or first_password_use.
    • task_thread_idthread id
      Set when the step belongs to a helper's thread and the task's events show it too.
  • worker.tool_resultevent
    A tool's answer to the worker.
    Show its 4 fields
    • tool_use_idevent id
      The worker.tool_use it answers.
    • statusstring
      How the tool ended. outcome_unknown is a started call that was stopped, when nobody can prove what it did.

      One of succeeded, failed, cancelled, outcome_unknown

    • outputJSON
      What the tool answered. Left out when it answered nothing.
    • errorobject
      Why the tool failed. Left out when it did not.
      Show its 2 fields
      • codestring
        The reason, such as approval_expired.
      • messagestring
        What went wrong.
  • worker.mcp_tool_useevent
    The worker called a tool of one of your apps.
    Show its 7 fields
    • idevent id
      The tool use id, as on a worker.tool_use.
    • app_namestring
      The app's name.
    • namestring
      The app's own name for the tool.
    • inputJSON
      What the worker sent it.
    • evaluated_permissionstring
      What decided the step: allow, ask or deny. Left out while nothing has decided it yet.

      One of allow, ask, deny

    • evaluationobject
      Why. Left out while nothing has decided the step, and on a step refused before any setting applied. Treat a type or a reason_code you do not know as a plain ask.
      Show its 2 fields
      • typestring
        always_allow and always_ask are the tool's own setting. auto is the automatic check. fixed_rule is an action that always asks, whatever the setting says.

        One of always_allow, always_ask, auto, fixed_rule

      • evaluated_permissionobject
        On auto and fixed_rule only: the decision. Its type is allow, ask or deny. A reason_code goes with ask and deny: on auto, high_risk for a block and indeterminate when the check was unsure, failed or ran out of time; on fixed_rule, one of send, pay, post, delete, change_settings or first_password_use.
    • task_thread_idthread id
      Set when the step belongs to a helper's thread and the task's events show it too.
  • worker.mcp_tool_resultevent
    The app's answer to the worker.
    Show its 5 fields
    • mcp_tool_use_idevent id
      The worker.mcp_tool_use it answers.
    • statusstring
      How the tool ended. outcome_unknown is a started call that was stopped, when nobody can prove what it did.

      One of succeeded, failed, cancelled, outcome_unknown

    • outputJSON
      What the tool answered. Left out when it answered nothing.
    • errorobject
      Why the tool failed. Left out when it did not.
      Show its 2 fields
      • codestring
        The reason, such as approval_expired.
      • messagestring
        What went wrong.
    • is_errorboolean
      true when the call failed.
  • worker.custom_tool_useevent
    The worker called one of its custom tools. Little Worker never runs a custom tool: the task waits with requires_action until a user.custom_tool_result answers. A permission check never applies to it, so it has no evaluated_permission.
    Show its 4 fields
    • idevent id
      The id your answer names in custom_tool_use_id.
    • namestring
      The tool's name.
    • inputJSON
      What the worker sent it.
    • task_thread_idthread id
      Set when the call belongs to a helper's thread and the task's events show it too.
  • worker.thread_context_compactedevent
    The worker's context grew too long and was squeezed into a summary. One event for each squeeze.
  • span.outcome_evaluation_startevent
    A check round of an outcome began.
    Show its 2 fields
    • outcome_idoutcome id
      The outcome, outc_ and 32 hex digits.
    • iterationinteger
      The round, counted from 0.
  • span.outcome_evaluation_ongoingevent
    A check round is still going. None is sent today; the type is listed so a client written for Claude's heartbeat reads this one.
    Show its 2 fields
    • outcome_idoutcome id
      The outcome, outc_ and 32 hex digits.
    • iterationinteger
      The round.
  • span.outcome_evaluation_endevent
    A check round ended.
    Show its 6 fields
    • outcome_idoutcome id
      The outcome, outc_ and 32 hex digits.
    • outcome_evaluation_start_idevent id
      The span.outcome_evaluation_start it closes.
    • iterationinteger
      The round.
    • resultstring
      How the round came out.

      One of satisfied, needs_revision, max_iterations_reached, failed, interrupted

    • explanationstring
      The grader's reasons, in plain words.
    • usageobject
      What the grader used. A check is one model call with no prompt cache, so the cache counts are 0.
      Show its 4 fields
      • input_tokensinteger
        Tokens read.
      • output_tokensinteger
        Tokens written.
      • cache_creation_input_tokensinteger
        Always 0.
      • cache_read_input_tokensinteger
        Always 0.
  • span.model_request_startevent
    The worker sent one request to the model. Listed before what the answer wrote.
  • span.model_request_endevent
    The request ended. Listed after what its answer wrote, and before the stop that follows.
    Show its 3 fields
    • model_request_start_idevent id
      The span.model_request_start it closes.
    • is_errorboolean or null
      true when the request failed, broke off or was stopped. Nothing is counted then.
    • model_usageobject
      What the request used.
      Show its 6 fields
      • input_tokensinteger
        Tokens read, without the prompt cache's tokens.
      • output_tokensinteger
        Tokens written.
      • cache_creation_input_tokensinteger
        Tokens written to the prompt cache.
      • cache_read_input_tokensinteger
        Tokens read from the prompt cache.
      • speedstring
        The speed it ran at.

        One of standard, fast

      • inference_geostring
        The region that served it.

        One of us, global

  • worker.thinkingevent
    The model reasoned in that request. A progress signal with no content, listed right after the request's start.
  • task.errorevent
    Something failed. An exhausted error comes right before the task.status_idle it caused.
    Show its 2 fields
    • errorobject
      What failed.
      Show its 8 fields
      • typestring
        The kind of failure.

        One of model_overloaded_error, model_rate_limited_error, model_request_failed_error, billing_error, app_connection_failed_error, app_authentication_failed_error, password_host_unreachable_error, repository_authentication_error, repository_forbidden_error, repository_not_found_error, repository_checkout_error, repository_clone_error, unknown_error

      • messagestring
        What went wrong.
      • retry_statusobject
        Whether Little Worker tries again, in its type: retrying (it goes on by itself), exhausted (the work stopped, and the task takes another message) or terminal (the task cannot go on).
      • codestring
        Little Worker's own code for the reason, such as password_host_blocked or repository_unavailable.
      • app_namestring
        On an app error: the app.
      • password_idpassword id
        On password_host_unreachable_error: the password whose site the worker's sites block. One comes for each such password, and the task goes on without it.
      • password_set_idpassword set id
        On password_host_unreachable_error: its set.
      • repository_urlstring or null
        On a repository error: the repository the task could not use. The task goes on without it.
    • capstring or null
      On a billing_error that is retrying: the plan, trial, balance or Limit cap that holds the task. Left out otherwise.
  • task.status_runningevent
    The task began running: its work was accepted, or it went on after it had stopped. One event for each change of status, so a start that follows a queue adds none.
    Show its 1 fields
    • run_idrun id
      The run, one stretch of the task's work.
  • task.usageevent
    What the task's work used, up to the stop that follows. One comes right before every task.status_idle.
    Show its 2 fields
    • usageobject
      The fields of the task's usage, counted at that stop. After an interrupt, the model call that was cut off is charged a few seconds later, so this event leaves it out. Read the event again later, or the task, to count it.
    • budgetobject
      The task's budget.
  • task.status_idleevent
    The worker stopped. Read stop_reason to know why.
    Show its 3 fields
    • run_idrun id
      The run, one stretch of the task's work.
    • stop_reasonobject
      Why the worker stopped.
      Show its 5 fields
      • typestring
        end_turn: its turn is over, also after an interrupt. requires_action: it waits for you or a person. retries_exhausted: the work failed, and a task.error came just before. budget_reached: the task's own budget holds it. refusal: the model declined the turn. turn_limit and time_limit: a run guard stopped the run cleanly, and a new message or a send back starts a fresh run.

        One of end_turn, requires_action, retries_exhausted, budget_reached, refusal, turn_limit, time_limit

      • event_idsarray of event ids
        With requires_action: the worker.tool_use, worker.mcp_tool_use and worker.custom_tool_use events it waits on. Answer a tool use with a user.approval, and a custom tool use with a user.custom_tool_result.
      • blockingarray of objects
        With requires_action: every step it waits on, in the order they were asked, cards in the app included.
        Show its 3 fields
        • typestring
          The kind of step.

          One of approval, auto_review, virtual_card, cookie, email_draft, slack_draft, question, form, password_request, handoff, hosted_decision, custom_tool_use

        • event_idevent id or null
          The worker.tool_use event of an approval, or the worker.custom_tool_use event of a custom_tool_use. null for every other kind, which is a card in the app.
        • person_onlyboolean
          true when only the person can answer it, in the app, and a key never can.
      • limitinteger
        With turn_limit: the most model turns a run takes.
      • limit_msinteger
        With time_limit: the most active model time a run takes, in milliseconds.
    • stop_detailsobject or null
      Why the model declined the turn, after a refusal. null for every other stop.
      Show its 3 fields
      • typestring
        Always refusal.
      • categorystring or null
        The policy that declined it, such as cyber, bio, frontier_llm, reasoning_extraction or general_harms. More may come.
      • explanationstring or null
        A sentence for people. Its wording can change, so do not parse it.
  • task.status_rescheduledevent
    A run hit a passing fault, and Little Worker runs it again by itself.
    Show its 1 fields
    • run_idrun id
      The run, one stretch of the task's work.
  • task.status_terminatedevent
    The task was taken back with POST /tasks/{task_id}/withdraw. It takes nothing more.
  • task.updatedevent
    The task was changed with POST /tasks/{task_id}. It shows what the change set.
    Show its 3 fields
    • titlestring or null
      The new title. Left out when the change did not set it.
    • metadataobject
      All of the task's labels, while any is left. Left out when the change did not set them.
    • budgetobject
      The new budget. Left out when the change did not set it.
  • task.deletedevent
    The task was deleted. An open stream reads it and ends. The task's other status events go with its history.
  • history.purgedevent
    On a plan that keeps history for a set time, such as the free plan, older history was deleted. A list that starts at the beginning shows the newest one first.
    Show its 1 fields
    • beforetime
      The cutoff: history older than this is gone.
  • task.thread_createdevent
    A helper's thread began. The worker handed it work.
    Show its 3 fields
    • task_thread_idthread id
      The thread of the helper this step is about.
    • worker_namestring
      The helper's name.
    • workflow_run_idworkflow run id or null
      The workflow run the thread belongs to, if any.
  • task.thread_status_runningevent
    The thread is at work.
    Show its 2 fields
    • task_thread_idthread id
      The thread of the helper this step is about.
    • worker_namestring
      The helper's name.
  • task.thread_status_idleevent
    The thread stopped.
    Show its 4 fields
    • task_thread_idthread id
      The thread of the helper this step is about.
    • worker_namestring
      The helper's name.
    • stop_reasonobject
      Why, in the same form as on task.status_idle. A thread can also stop with requires_action, which waits on the event_ids it lists.
    • stop_detailsobject or null
      As on task.status_idle.
  • task.thread_status_rescheduledevent
    A cap holds the thread. It goes on when there is room.
    Show its 2 fields
    • task_thread_idthread id
      The thread of the helper this step is about.
    • worker_namestring
      The helper's name.
  • task.thread_status_terminatedevent
    The thread ended.
    Show its 2 fields
    • task_thread_idthread id
      The thread of the helper this step is about.
    • worker_namestring
      The helper's name.
  • worker.thread_message_sentevent
    The instructions the worker sent a helper.
    Show its 3 fields
    • to_task_thread_idthread id
      The helper's thread.
    • to_worker_namestring or null
      The helper's name.
    • contentarray of content blocks
      The instructions, as text blocks.
  • worker.thread_message_receivedevent
    The answer a helper sent back.
    Show its 3 fields
    • from_task_thread_idthread id
      The helper's thread.
    • from_worker_namestring or null
      The helper's name.
    • contentarray of content blocks
      The answer, as text blocks.
  • workflow_run.createdevent
    A workflow run started: a plan of named phases, each a set of helpers that work side by side. No webhook hears a workflow run's events.
    Show its 4 fields
    • workflow_run_idworkflow run id
      The workflow run, wrun_ and 32 hex digits.
    • namestring
      The run's name.
    • descriptionstring or null
      What the run is for.
    • phasesarray of objects
      Its phases, in order: each has an id (wrph_ and 32 hex digits), a name, and a description that may be null.
  • workflow_run.status_runningevent
    The run is at work, again after a pause.
    Show its 1 fields
    • workflow_run_idworkflow run id
      The workflow run, wrun_ and 32 hex digits.
  • workflow_run.status_idleevent
    The run paused: a helper is held at the task's budget, or an interrupt paused it.
    Show its 1 fields
    • workflow_run_idworkflow run id
      The workflow run, wrun_ and 32 hex digits.
  • workflow_run.status_endedevent
    The run ended. It is always the run's last event.
    Show its 2 fields
    • workflow_run_idworkflow run id
      The workflow run, wrun_ and 32 hex digits.
    • resultobject
      How it ended, in its type: completed, stopped, or error with the error.
  • workflow_run.errorevent
    The run's error, right before its end, or a start that made no run.
    Show its 2 fields
    • workflow_run_idworkflow run id or null
      null when the error kept a run from being made.
    • errorobject
      What went wrong. It never holds what the run wrote.
      Show its 2 fields
      • typestring
        The kind of error.

        One of timeout_error, program_error, unknown_error, thread_limit_error, max_workflow_runs_error

      • messagestring
        A sentence that is safe to log.
  • workflow_run.phase_startedevent
    A phase began.
    Show its 2 fields
    • workflow_run_idworkflow run id
      The workflow run, wrun_ and 32 hex digits.
    • workflow_run_phase_idworkflow run phase id
      The phase.
  • workflow_run.phase_endedevent
    A phase ended, or the run's end closed it.
    Show its 3 fields
    • workflow_run_idworkflow run id
      The workflow run, wrun_ and 32 hex digits.
    • workflow_run_phase_idworkflow run phase id
      The phase.
    • phase_started_idevent id
      The workflow_run.phase_started it closes.
Event object
{
  "type": "worker.message",
  "id": "event_3c8e1f5a7b2d4c9e8a0b6d4f2e1c9a7b",
  "task_id": "task_01JA8Z6Q4M3T9V2K7X5R1B0C8D",
  "sequence": "10",
  "processed_at": "2026-10-25T16:04:30.771Z",
  "message_id": "message_a41d7e9c2b5f4083b6e1d9c7a5f3b2e0",
  "content": [
    {
      "type": "text",
      "text": "1. Open at 7 am, starting Monday\n2. Up early: open at 7 am from Monday\n3. Coffee is on at 7 am"
    }
  ],
  "sources": []
}

List events

GET/tasks/{task_id}/events

List what happened in a task, oldest first. Each entry is an event, as the event object shows it. Keep only some types, or only some times, or start after an event you already have.

The time filters compare each event's processed_at, to the millisecond. A message still waiting (processed_at is null) matches no time filter. A page works only with the order and the filters that made it.

On a plan that keeps history for a set time, such as the free plan, older history is deleted. The chat then gets a history.purged event, whose before is the cutoff. A list from the start begins with the newest one, then the events that are kept.

Path parameters

  • task_idtask idRequired
    The task, such as task_01JA8Z6Q4M3T9V2K7X5R1B0C8D.

Query parameters

  • types[]string
    Only events of this type. Repeat it to name more than one.

    One of user.message, system.message, user.define_outcome, user.approval, user.interrupt, user.tool_result, user.custom_tool_result, worker.message, worker.tool_use, worker.tool_result, worker.mcp_tool_use, worker.mcp_tool_result, worker.custom_tool_use, worker.thread_context_compacted, span.outcome_evaluation_start, span.outcome_evaluation_ongoing, span.outcome_evaluation_end, span.model_request_start, span.model_request_end, worker.thinking, task.error, task.status_running, task.usage, task.status_idle, task.status_rescheduled, task.status_terminated, task.updated, task.deleted, history.purged, task.thread_created, task.thread_status_running, task.thread_status_idle, task.thread_status_rescheduled, task.thread_status_terminated, worker.thread_message_sent, worker.thread_message_received, workflow_run.created, workflow_run.status_running, workflow_run.status_idle, workflow_run.status_ended, workflow_run.error, workflow_run.phase_started, workflow_run.phase_ended

  • afterstring
    Only events after this sequence. A whole number as a string, such as "42". "0" is the start.
  • limitinteger
    How many to answer, 1 to 100. The default is 20.
  • pagestring
    The next_page of the answer before, to read the next page.
  • orderstring
    asc, oldest first (the default), or desc, newest first.

    One of asc, desc

  • created_at[gt]time
    Only events written after this time. created_at[gte], created_at[lt] and created_at[lte] work the same way.

Returns

A list: data holds the events, and next_page is the page to send for the next ones (null on the last page). Unlike the task list, there is no prev_page.

Errors

StatusTypeWhen
400invalid_request_errortask_id is not a task id. error_code is validation_error.
400invalid_request_errorA filter is not valid: a limit outside 1 to 100, a types[] value that is not an event type, or a time that is not RFC 3339. error_code is validation_error.
400invalid_request_errorThe cursor is refused. error_code is invalid_cursor when page or after cannot be read, or page was made with another order or filter; cursor_ahead when it is past the newest event; and cursor_expired when it is older than the history kept, or from before a history.purged that deleted events it had not read. For the last two, list the task's events from the start.
401authentication_errorThe key is missing, removed, or not a key. Send it in x-api-key.
404not_found_errorNo task in the key's workspace has this id, or it was taken back or deleted (Task not found.).
429rate_limit_errorThe workspace made too many requests this minute. Wait the seconds in Retry-After, then try again.
curl "https://core.thelittleworker.com/api/v1/tasks/task_01JA8Z6Q4M3T9V2K7X5R1B0C8D/events?types%5B%5D=worker.message&order=desc&limit=1" \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19"
Response200
{
  "data": [
    {
      "type": "worker.message",
      "id": "event_3c8e1f5a7b2d4c9e8a0b6d4f2e1c9a7b",
      "task_id": "task_01JA8Z6Q4M3T9V2K7X5R1B0C8D",
      "sequence": "10",
      "processed_at": "2026-10-25T16:04:30.771Z",
      "message_id": "message_a41d7e9c2b5f4083b6e1d9c7a5f3b2e0",
      "content": [
        {
          "type": "text",
          "text": "1. Open at 7 am, starting Monday\n2. Up early: open at 7 am from Monday\n3. Coffee is on at 7 am"
        }
      ],
      "sources": []
    }
  ],
  "next_page": null
}

Send events

POST/tasks/{task_id}/events

Send a task one event or more. A message tells the worker what you want, and the other events stop it, answer it, or judge its result.

Every event is checked before any applies, and they apply in order. If one fails, none applies. Any mix is taken, except that user.accept and user.send_back are each sent alone.

Event What it does
user.message A message the worker reads after the work in hand.
user.interrupt Stops the work in hand.
user.interrupt, then user.message Stops the work in hand, then the message starts a new turn: task.status_idle with end_turn, then task.status_running.
user.accept Accepts the result.
user.send_back Sends the result back for more work.
user.approval Allows or denies an action that waits for an answer.
user.custom_tool_result Answers a custom tool the worker called.
user.define_outcome Sets what the task's work is checked against.
user.message, then system.message The worker reads the system message in its model's system role, from the turn the message starts.

A message sent while the worker is busy waits for its next turn. The list and the stream place it where it was accepted. An interrupt ends the stopped turn at once, so after an interrupt and a message, the task.status_idle comes before the user.message.

A worker takes only a user.message as your intent. Text it reads in a page, a file, an email or a tool's answer is information, not an instruction.

Path parameters

  • task_idtask idRequired
    The task, such as task_01JA8Z6Q4M3T9V2K7X5R1B0C8D.

Headers

  • Idempotency-KeyUUID
    Send one so a retry sends the events once. A second call with the same key answers what the first call did. The same key with other events is 409 with error_code idempotency_conflict. Kept for 7 days.

Body

  • eventsarray of eventsRequired
    1 to 50 events, each one of these kinds. It is applied in the order you give them.
    Show its 8 fields
    • user.messageevent
      A message the worker reads after the work in hand. Sent to a task that is done, it reopens the task, as a send back does, with the message as the reason.
      Show its 2 fields
      • typestringRequired
        user.message.
      • contentarray of content blocksRequired
        One block or more. A text block is { "type": "text", "text": "..." }. An image is { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": "..." } }. A document is the same with "type": "document" and a source of base64 application/pdf, or { "type": "text", "media_type": "text/plain", "data": "..." }; it may also have a title and a context. At most six images and documents go on one message. A url or file source is refused for now.
    • user.interruptevent
      Stops the work in hand, which ends the turn: task.status_idle with end_turn. A user.message after it starts a new turn. A task held at its own budget has nothing to stop, and an interrupt does nothing.
      Show its 1 fields
      • typestringRequired
        user.interrupt.
    • user.acceptevent
      Accepts the result. Finished work is done by itself, so this matters only for work that needed a look. Accepting twice changes nothing. Send it alone.
      Show its 1 fields
      • typestringRequired
        user.accept.
    • user.send_backevent
      Sends the result back for more work. The reason is the next message the worker reads. Send it alone.
      Show its 2 fields
      • typestringRequired
        user.send_back.
      • reasonstringRequired
        What to change, up to 4,000 characters.
    • user.approvalevent
      Allows or denies an action that waits for an answer. It needs a key made with Can allow actions. It answers once, and a key cannot choose to remember an answer.
      Show its 4 fields
      • typestringRequired
        user.approval.
      • tool_use_idevent idRequired
        One of the event_ids of the task's requires_action stop: the tool use that asks.
      • resultstringRequired
        The answer.

        One of allow, deny

      • deny_messagestring
        Why, up to 10,000 characters. Only with deny; with allow the send is refused. The worker reads it as the reason the action failed.
    • user.custom_tool_resultevent
      Answers a worker.custom_tool_use the task waits on. The worker reads content as the tool's result.
      Show its 4 fields
      • typestringRequired
        user.custom_tool_result.
      • custom_tool_use_idevent idRequired
        One of the event_ids of the task's requires_action stop.
      • contentarray of text blocks
        The result. Text blocks only, for now.
      • is_errorboolean
        true to give the worker a failure instead of a result.
    • user.define_outcomeevent
      Sets what the task's work is checked against. Each check round says whether the work met it.
      Show its 5 fields
      • typestringRequired
        user.define_outcome.
      • descriptionstring
        What done looks like. Left out, it is the task's ask.
      • rubricobject
        The check, as { "type": "text", "content" } or { "type": "file", "file_id" }, up to 262,144 characters.
      • done_meansarray of strings
        Instead of a rubric: 1 to 8 checks of 1 to 200 characters, graded one by one.
      • max_iterationsinteger
        How many check rounds, 1 to 20. The default is 3.
    • system.messageevent
      A message the model reads in its system role, from the turn the message before it starts. It never steers the work in hand. Send one at most, as the last event, right after a user.message.
      Show its 2 fields
      • typestringRequired
        system.message.
      • contentarray of text blocksRequired
        1 to 1,000 text blocks of at least one character.

Returns

200 and { "data": [...] }, one entry for each event you sent, in the order you sent them. A send is not a list, so there is no next_page. Each entry is one of these.

  • user.messageevent
    The message as stored, the same as the list shows it.
  • system.messageevent
    The system message as stored, right after its message.
  • user.interruptevent
    What the interrupt stopped.
    Show its 2 fields
    • typestring
      user.interrupt.
    • run_idrun id or null
      The run it stopped. null when no work was in hand to stop.
  • user.acceptevent
    The accepted result.
    Show its 2 fields
    • typestring
      user.accept.
    • taskobject
      The task, as it stands now.
  • user.send_backevent
    The result, sent back.
    Show its 3 fields
    • typestring
      user.send_back.
    • taskobject
      The task, running again.
    • run_idrun id
      The new run.
  • user.approvalevent
    The answer, taken.
    Show its 3 fields
    • typestring
      user.approval.
    • tool_use_idevent id
      The tool use you answered.
    • statusstring
      What you decided.

      One of allowed, denied

  • user.custom_tool_resultevent
    The answer, taken.
    Show its 3 fields
    • typestring
      user.custom_tool_result.
    • custom_tool_use_idevent id
      The custom tool use you answered.
    • is_errorboolean
      Whether you gave a failure.
  • user.define_outcomeevent
    The outcome, as set.
    Show its 7 fields
    • typestring
      user.define_outcome.
    • outcome_idoutcome id
      The outcome, outc_ and 32 hex digits.
    • processed_attime
      When it was set.
    • descriptionstring
      What done looks like.
    • rubricobject
      The check.
    • done_meansarray of strings or null
      The checks, when you sent them in place of a rubric.
    • max_iterationsinteger
      How many check rounds.

Errors

StatusTypeWhen
400invalid_request_errortask_id is not a task id. error_code is validation_error.
400invalid_request_errorAn event is not valid: a missing field, an id of the wrong kind, an event the route does not take, more than 50 events, an Idempotency-Key that is not a UUID, or a deny_message with allow. message says which. error_code is validation_error.
400invalid_request_errorThe mix of events is not allowed: user.accept or user.send_back with another event, a system.message that is not the last event or does not follow a user.message, a redacted block, an image that is not an image type, a document that is not a PDF, data that is not base64, or more than six images and documents on a message.
400invalid_request_errorThe task reached its own budget (error_code budget_reached). It takes only approvals, tool results and interrupts until you raise its budget with Change a task.
400invalid_request_errorA system.message for a task whose model has no system role (error_code model_does_not_support_mid_conversation_system).
401authentication_errorThe key is missing, removed, or not a key. Send it in x-api-key.
403permission_errorA user.approval from a key made without Can allow actions, or for a step only the person may answer, such as a worker's first use of a saved password or an email it sends (error_code person_required). On a shared workspace, a key can answer only approvals of tasks its own person started (not_task_starter), and of a Mac its own person paired (not_allowlisted).
404not_found_errorNo task has this id (Task not found.), no approval of this task has that tool_use_id (Approval not found.), or no custom tool use of this task has that custom_tool_use_id.
409conflict_errorThe task is archived and takes no message: give a new task. Or user.accept came while the worker is still on the task: wait for the result, or interrupt, then accept. Or a done task that belongs to a project was sent back or sent a message: answer in the app.
409conflict_errorThe approval was taken back, already has the other answer, or the run was cancelled. Or error_code is approval_consumed (the action already ran) or approval_expired (a Mac action's time ran out). Or the custom tool use is not waiting for a result.
409conflict_errorThe Idempotency-Key was already used with other events (error_code idempotency_conflict). Use a new key for new events.
429rate_limit_errorThe workspace made too many requests this minute (error_code rate_limited), or the worker already has 20 waiting runs (queue_limit, and retryable is true). Wait the seconds in Retry-After, or send the message again once the worker has caught up.
curl "https://core.thelittleworker.com/api/v1/tasks/task_01JA8Z6Q4M3T9V2K7X5R1B0C8D/events" \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "Idempotency-Key: 2f6c9a1e-8b3d-4a57-9e0c-4d1b7f5a3c82" \
  -H "content-type: application/json" \
  -d '{
  "events": [
    {
      "type": "user.message",
      "content": [
        {
          "type": "text",
          "text": "The shop is called Field Day."
        }
      ]
    }
  ]
}'
Response200
{
  "data": [
    {
      "type": "user.message",
      "id": "event_c9e7a5b3d1f24680a2c4e6f8b0d13579",
      "task_id": "task_01JA8Z6Q4M3T9V2K7X5R1B0C8D",
      "sequence": "12",
      "processed_at": null,
      "message_id": "message_5b3d1f9e7c2a4860b4d2f0e8c6a49371",
      "content": [
        {
          "type": "text",
          "text": "The shop is called Field Day."
        }
      ]
    }
  ]
}

Stream events

GET/tasks/{task_id}/events/stream

Follow a task as it works. The answer stays open and sends each event as it is written, as Server-Sent Events. It ends when you close it, or after a task.deleted event.

With no after and no Last-Event-ID, the stream sends only the events written after it opens. after=0 replays every event that is kept, from the first. After a drop, connect again with the id of the last frame you read as Last-Event-ID (or as after), and the stream sends only what came later. If you send both, the header is used.

Status changes (task.updated, task.status_terminated and task.deleted), model request spans, worker.thinking and outcome events are kept outside the chat. Each carries the sequence of the chat event before it, so one can be written at a sequence you already took as your cursor. A cursor you hold goes on after the chat event it names, and still includes these events at its own sequence. Nothing is missed, and one you already saw can come again, so skip event ids you have seen.

To see the worker's answer as it is written, add event_deltas[]=worker.message to the URL. Each preview is an event_start frame, then event_delta frames with the text so far. Previews have no id, go only to the connection that asked, only for text written after it opened, and are never listed or replayed. The finished worker.message holds the whole text and is the record. event_deltas[]=worker.thinking adds a start-only preview: an event_start with no deltas, right before the worker.thinking it names.

A workspace may hold 10 streams open at once. Past that, a new stream is 429.

Path parameters

  • task_idtask idRequired
    The task, such as task_01JA8Z6Q4M3T9V2K7X5R1B0C8D.

Query parameters

  • afterstring
    Send only events after this sequence. A whole number as a string, such as "42". "0" is the start.
  • event_deltas[]string
    A kind of event to preview while it is written. Repeat it to name both.

    One of worker.message, worker.thinking

Headers

  • Last-Event-IDstring
    The id of the last frame you read, which is its event's sequence. The stream sends only what came after it.

Returns

200 and a stream, text/event-stream. Each frame is an id (the event's sequence), an event (its type) and data, the event as JSON. The response shown is the data of one frame.

Text
id: 10
event: worker.message
data: {"type":"worker.message","sequence":"10",...}

id: 11
event: task.status_idle
data: {"type":"task.status_idle","sequence":"11",...}

A preview frame has no id. Its data is { "type": "event_start", "event": { "type", "id" } }, where id is the id the finished message will carry, or { "type": "event_delta", "event_id", "delta" }, where delta is { "type": "content_delta", "index", "content": { "type": "text", "text" } }. A line that starts with a colon is a comment that keeps the connection open, so your reader skips it.

Errors

StatusTypeWhen
400invalid_request_errortask_id is not a task id. error_code is validation_error.
400invalid_request_errorevent_deltas[] names something other than worker.message or worker.thinking, or has more than 100 values (error_code validation_error).
400invalid_request_errorThe cursor is refused. error_code is invalid_cursor when page or after cannot be read, or page was made with another order or filter; cursor_ahead when it is past the newest event; and cursor_expired when it is older than the history kept, or from before a history.purged that deleted events it had not read. For the last two, list the task's events from the start.
401authentication_errorThe key is missing, removed, or not a key. Send it in x-api-key.
404not_found_errorNo task in the key's workspace has this id, or it was taken back or deleted (Task not found.).
429rate_limit_errorThe workspace made too many requests this minute, or already has 10 streams open (error_code rate_limited). Wait the seconds in Retry-After, or close a stream you no longer read.
curl -N "https://core.thelittleworker.com/api/v1/tasks/task_01JA8Z6Q4M3T9V2K7X5R1B0C8D/events/stream?after=0" \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19"
Response200
{
  "type": "task.status_idle",
  "id": "event_8f2a6c4e1b9d4375a0c8e6d4b2f19753",
  "task_id": "task_01JA8Z6Q4M3T9V2K7X5R1B0C8D",
  "sequence": "11",
  "processed_at": "2026-10-25T16:04:31.209Z",
  "run_id": "run_6d2e8f1a3c5b4d7e9f0a1b2c3d4e5f6a",
  "stop_reason": {
    "type": "end_turn"
  },
  "stop_details": null
}

Type a few words, like “give a task” or “errors”.