---
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', 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). ``incident`` and ``alerts`` have mode blocks today; ``escalation`` and others land as localized drop-ins (their standing block + tooling) when that work begins.
        - `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', 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). ``incident`` and ``alerts`` have mode blocks today; ``escalation`` and others land as localized drop-ins (their standing block + tooling) when that work begins.
        - `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', 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). ``incident`` and ``alerts`` have mode blocks today; ``escalation`` and others land as localized drop-ins (their standing block + tooling) when that work begins.
    - `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

---

[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/16ecc819c413/schema)
