---
title: "Deploy Workers Bulk"
method: POST
path: "/workers/bulk"
tags: ["workers"]
---

# Deploy Workers Bulk

`POST /workers/bulk`

Deploy several workers in one request.

Each item is a normal deploy. Failures stay per-item so a stopped
channel does not roll back the ones that already started. The
organization and deploying identity come from the session, never the
body.

``201`` when every item succeeds; ``207 Multi-Status`` when any fail.

## Request body

- BulkDeployWorkersRequest — Deploy several workers in one request. Each item is a normal ``DeployWorkerRequest``. Failures are per-item so a stopped channel does not roll back the ones that already started.
  - `workers` union[], required
    - union
      - SlackDeployWorkerRequest — Deploy a worker onto an existing Slack channel (PRD-3444). The non-channel-join origination path: the operator picks the channel and the mode instead of a rule matching on join. ``mode`` is required — an explicit deploy always states the worker's standing mission, rather than inheriting a rule default that doesn't exist on this path.
        - `platform` 'slack'
        - `channel_id` string, required
        - `mode` 'incident' | 'alerts' | 'custom', required — The kind of channel the agent lives in — its standing mission. Drives the swappable mode block in the system prompt (and, later, the toolset). ``escalation`` and others land as localized drop-ins (their standing block + tooling) when that work begins. ``incident`` and ``alerts`` each state a mission the worker owns whatever the operator writes. ``custom`` states none: it supplies only the conduct floor the identity block forward-references, and the operator's ``custom_instruction`` is what gives the worker something to do. A channel that is neither working an incident nor triaging alert fires gets this one rather than being told it is in an incident (PRD-3766). The value is what customer-facing surfaces render, so it reads as the operator's own word for it rather than as an internal category. A member's *name* is persisted, in ``resident_agent_workflow.mode`` and ``resident_agent_template.mode`` — plain varchars with no DB-side value set — and read back through this enum, so adding a member is one deploy but removing or renaming one orphans every row holding it. It also makes a member forward-only: code that predates it raises on a row that carries it, rather than degrading. So a new member wants its read deployed everywhere before anything can write it — either landing the member ahead of the paths that create it, or gating creation while it rolls out.
        - `custom_instruction` string, nullable
      - WebDeployWorkerRequest — Deploy a channel-less web worker (PRD-3495). No ``channel_id``: a web worker has no external channel to bind to and mints its own handle at origination, so there is nothing for the operator to pick. That is the whole reason this is a separate shape rather than an optional field — a web deploy that accepted a channel id would be accepting something it must ignore. ``name`` is required for the same asymmetry. Slack reads a channel's name; web has no external source for one, so an unnamed web worker would render in ``/workers`` as its ``resource_id`` — a uuid. There is no rename endpoint, so this is where the name is set.
        - `platform` 'web', required
        - `name` string, required
        - `mode` 'incident' | 'alerts' | 'custom', required — The kind of channel the agent lives in — its standing mission. Drives the swappable mode block in the system prompt (and, later, the toolset). ``escalation`` and others land as localized drop-ins (their standing block + tooling) when that work begins. ``incident`` and ``alerts`` each state a mission the worker owns whatever the operator writes. ``custom`` states none: it supplies only the conduct floor the identity block forward-references, and the operator's ``custom_instruction`` is what gives the worker something to do. A channel that is neither working an incident nor triaging alert fires gets this one rather than being told it is in an incident (PRD-3766). The value is what customer-facing surfaces render, so it reads as the operator's own word for it rather than as an internal category. A member's *name* is persisted, in ``resident_agent_workflow.mode`` and ``resident_agent_template.mode`` — plain varchars with no DB-side value set — and read back through this enum, so adding a member is one deploy but removing or renaming one orphans every row holding it. It also makes a member forward-only: code that predates it raises on a row that carries it, rather than degrading. So a new member wants its read deployed everywhere before anything can write it — either landing the member ahead of the paths that create it, or gating creation while it rolls out.
        - `custom_instruction` string, nullable

## Response `201`

Successful Response

- BulkDeployWorkersResponse
  - `deployed` WorkerResponse[], required
    - `id` string, required
    - `resource_id` string, required
    - `display_name` string, required
    - `platform` 'slack' | 'teams' | 'web', required — A supported chat platform. Values align with ``integration_instances.type`` so a chat install's platform and its integration row stay in lockstep.
    - `agent_type` string, required
    - `mode` 'incident' | 'alerts' | 'custom', required — The kind of channel the agent lives in — its standing mission. Drives the swappable mode block in the system prompt (and, later, the toolset). ``escalation`` and others land as localized drop-ins (their standing block + tooling) when that work begins. ``incident`` and ``alerts`` each state a mission the worker owns whatever the operator writes. ``custom`` states none: it supplies only the conduct floor the identity block forward-references, and the operator's ``custom_instruction`` is what gives the worker something to do. A channel that is neither working an incident nor triaging alert fires gets this one rather than being told it is in an incident (PRD-3766). The value is what customer-facing surfaces render, so it reads as the operator's own word for it rather than as an internal category. A member's *name* is persisted, in ``resident_agent_workflow.mode`` and ``resident_agent_template.mode`` — plain varchars with no DB-side value set — and read back through this enum, so adding a member is one deploy but removing or renaming one orphans every row holding it. It also makes a member forward-only: code that predates it raises on a row that carries it, rather than degrading. So a new member wants its read deployed everywhere before anything can write it — either landing the member ahead of the paths that create it, or gating creation while it rolls out.
    - `custom_instruction` string, nullable
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `stopped_at` string, date-time, nullable, required
    - `status` 'critical' | 'attention' | 'working' | 'stable', required — The worker's present-tense status top-line, shown as a pill on the /workers list. Only these agent-picked values are ever stored in the `status` column; a null column means none has been emitted yet (the UI renders that as `starting`), and the terminal `stopped` state is derived from `stopped_at` — neither is stored here, so there is no second source of truth. The agent-facing definitions live on the ``status`` field in ``prompts.py`` (the only copy the model reads); keep this summary in sync with it. Values: CRITICAL: Immediate coordinated response is warranted because user-facing impact is severe or escalating. ATTENTION: A specific person or team should act now, but immediate coordinated incident response is not warranted. STABLE: Resting floor — no specific person or team needs to act now and no coordinated incident response is warranted. WORKING: Legacy — no longer emittable, see ``EmittableWorkerStatus``.
    - `status_message` string, nullable, required
    - `status_updated_at` string, date-time, nullable, required
  - `failures` BulkDeployWorkerFailure[], required
    - `index` integer, required
    - `code` 'AUTH_PROVIDER_UNAVAILABLE' | 'AUTH_PROVIDER_REQUEST_REJECTED' | 'AUTH_TOKEN_INVALID' | 'AUTH_TOKEN_EXPIRED' | 'AUTH_REFRESH_FAILED' | 'AUTH_CODE_EXCHANGE_FAILED' | 'AUTH_MEMBERSHIP_STORE_UNAVAILABLE' | 'AUTH_ORGANIZATION_VERIFICATION_THROTTLED' | 'AUTH_REQUEST_REJECTED' | 'AUTH_TRANSACTION_INVALID' | 'AUTH_ROUTING_UNAVAILABLE' | 'AUTH_REFRESH_HANDOFF' | 'ACCOUNT_LINKED_REAUTH_REQUIRED' | 'ACCOUNT_LINK_CONFLICT' | 'TOOL_HEALTH_CHECK_INVALID_SELECTION' | 'TOOL_HEALTH_CHECK_RUN_NOT_FOUND' | 'TOOL_HEALTH_CHECK_RUN_NOT_DELETABLE' | 'PRODUCT_FLOW_E2E_INVALID_SELECTION' | 'PRODUCT_FLOW_E2E_RUN_NOT_FOUND' | 'PRODUCT_FLOW_E2E_RUN_NOT_DELETABLE' | 'PRODUCT_FLOW_E2E_NOT_CONFIGURED' | 'PRODUCT_FLOW_E2E_LAUNCH_FAILED' | 'INVALID_INCIDENT_TIME' | 'SESSION_TITLE_TOO_LONG' | 'USER_MESSAGE_TOO_LONG' | 'AGENT_RUN_NOT_CONTROLLABLE' | 'AGENT_CONTROL_UNAVAILABLE' | 'PROMPT_MANIFEST_ORG_NOT_FOUND' | 'WORKER_FEED_UNAVAILABLE' | 'WORKER_NOT_FOUND' | 'WORKER_PIN_LIMIT_EXCEEDED' | 'WORKER_ROUTING_UNAVAILABLE' | 'WORKER_CHANNEL_NOT_FOUND' | 'WORKER_ALREADY_DEPLOYED' | 'WORKER_STOPPED_IN_CHANNEL' | 'WORKER_CHANNEL_READ_ONLY' | 'WORKER_DEPLOY_FAILED' | 'WORKER_REMOVE_FAILED' | 'WORKER_REMOVE_NOT_SUPPORTED' | 'WORKER_UPDATE_NOT_SUPPORTED' | 'PRIVATE_CHAT_NESTING_NOT_SUPPORTED' | 'WORKER_SCHEDULE_NOT_FOUND' | 'WORKER_SCHEDULE_INVALID' | 'WORKER_SCHEDULE_TITLE_GENERATION_UNAVAILABLE' | 'WORKER_SCHEDULE_ORCHESTRATION_UNAVAILABLE' | 'SLACK_INSTALL_NOT_CONFIGURED' | 'TEAMS_CLAIM_TOKEN_INVALID' | 'TEAMS_INSTALL_NOT_FOUND' | 'TEAMS_INSTALL_ALREADY_CLAIMED' | 'TEAMS_INSTALL_NOT_CONFIGURED' | 'TASK_BANK_NOT_FOUND' | 'TASK_BANK_STORAGE_UNAVAILABLE' | 'ANALYTICS_METRIC_HAS_NO_DETAIL' | 'AGENT_KNOWLEDGE_NOT_FOUND' | 'AGENT_KNOWLEDGE_INVALID_PATH_OR_SCOPE' | 'AGENT_KNOWLEDGE_CONFLICT' | 'AGENT_KNOWLEDGE_PRECONDITION_FAILED' | 'AGENT_KNOWLEDGE_INVALID_CONTENT' | 'AGENT_KNOWLEDGE_STORAGE_UNAVAILABLE' | 'AGENT_KNOWLEDGE_STORAGE_INCONSISTENT' | 'AGENT_KNOWLEDGE_PROCESSING_UNAVAILABLE' | 'AGENT_KNOWLEDGE_VERSION_READ_ONLY' | 'WORKER_TEMPLATE_NOT_FOUND' | 'WORKER_TEMPLATE_NAME_CONFLICT' | 'WORKER_TEMPLATE_INVALID' | 'SERVICE_ACCOUNT_NOT_FOUND' | 'SERVICE_ACCOUNT_NAME_CONFLICT' | 'TRIGGER_RULE_NOT_FOUND' | 'CHAT_INTEGRATION_NOT_FOUND' | 'WORKSPACE_SCAN_NOT_FOUND' | 'WEBHOOK_CONNECTION_NOT_FOUND' | 'WEBHOOK_CONNECTION_CONFLICT' | 'WEBHOOK_CONNECTION_SETUP_EXPIRED' | 'WEBHOOK_CONNECTION_INVALID_LINK' | 'DATASET_AVAILABILITY_UNAVAILABLE' | 'INTERNAL_ERROR', required — Stable, additive error codes. Add new codes; never rename existing ones.
    - `message` string, required
    - `channel_id` string, nullable
    - `name` string, nullable

## Other responses

- `207` — One or more workers could not be deployed.
- `422` — Validation Error

## Changes

> 27 revisions in range; 7 not diffed.

- **2026-09-02** `3025330cc50d` — 2 warning, 2 info
  - added the new `custom` enum value to the `deployed/items/mode/anyOf[subschema #1: Mode]/` response property for the response status `201`
  - added the new `custom` enum value to the `deployed/items/mode/anyOf[subschema #1: Mode]/` response property for the response status `207`
  - added the new `custom` enum value to the request property `workers/items/anyOf[subschema #1: SlackDeployWorkerRequest]/mode`
  - added the new `custom` enum value to the request property `workers/items/anyOf[subschema #2: WebDeployWorkerRequest]/mode`

[Change history](https://skmtc.dev/traversal/apis/fastapi/changes/workers/bulk/post.md)

---

[API](https://skmtc.dev/traversal/apis/fastapi.md) · [All operations](https://skmtc.dev/traversal/apis/fastapi/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/traversal/fastapi/revisions/3025330cc50d/schema)
