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

# Deploy Workers Bulk

`POST /workers/bulk`

## Request

- Base URL: `https://api.traversal.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.traversal.com/workers/bulk`
- Auth: none declared

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. The same names are stored in usage policy bodies. Renaming or removing a member stops a body naming it from parsing, and that disables the template's policies.
        - `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. The name is set initially here and can later be changed through the worker update endpoint.
        - `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. The same names are stored in usage policy bodies. Renaming or removing a member stops a body naming it from parsing, and that disables the template's policies.
        - `custom_instruction` string, nullable
      - TeamsDeployWorkerRequest: Deploy a worker onto an existing Microsoft Teams channel. The Teams counterpart of ``SlackDeployWorkerRequest``: the operator picks the channel and the mode instead of a rule matching on join. ``channel_id`` is the Teams conversation id (``19:...@thread.tacv2``), the identity a Teams channel is keyed by. ``mode`` is required — an explicit deploy always states the worker's standing mission. ``name`` is accepted because the deploy UI exposes the same worker-name input it uses for web, but it is deliberately ignored: a Teams channel already has a real name, and ``/workers`` renders that channel name, so honoring an override would make the list disagree with what people see in Teams. Kept on the wire (rather than forbidden) so the shared UI form still validates; see ``deploy_teams_worker``.
        - `platform` 'teams', required
        - `channel_id` string, 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. The same names are stored in usage policy bodies. Renaming or removing a member stops a body naming it from parsing, and that disables the template's policies.
        - `custom_instruction` string, nullable

## Response `201`

Successful Response

- BulkDeployWorkersResponse
  - `deployed` WorkerResponse[], required
    - `id` string, required
    - `organization_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.
    - `channel_url` string, nullable, required
    - `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. The same names are stored in usage policy bodies. Renaming or removing a member stops a body naming it from parsing, and that disables the template's policies.
    - `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' | 'ALERT_NOT_FOUND' | 'INVALID_INCIDENT_TIME' | 'SESSION_TITLE_TOO_LONG' | 'USER_MESSAGE_TOO_LONG' | 'INVESTIGATION_USAGE_LIMIT_EXCEEDED' | 'INVESTIGATION_START_REQUIREMENTS_UNMET' | 'AGENT_RUN_NOT_CONTROLLABLE' | 'AGENT_CONTROL_UNAVAILABLE' | 'PROMPT_MANIFEST_ORG_NOT_FOUND' | 'WORKER_FEED_UNAVAILABLE' | 'WORKER_EVENTS_SEEK_INVALID' | '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_CHANNEL_JOIN_REQUIRED' | 'WORKER_DEPLOY_FAILED' | 'WORKER_REMOVE_FAILED' | 'WORKER_REMOVE_NOT_SUPPORTED' | 'WORKER_UPDATE_NOT_SUPPORTED' | 'WORKER_UPDATE_INVALID' | 'PRIVATE_CHAT_NESTING_NOT_SUPPORTED' | 'PRIVATE_CHAT_ARCHIVED' | 'WORKER_SCHEDULE_NOT_FOUND' | 'WORKER_SCHEDULE_INVALID' | 'WORKER_SCHEDULE_TITLE_GENERATION_UNAVAILABLE' | 'WORKER_SCHEDULE_ORCHESTRATION_UNAVAILABLE' | 'SLACK_INSTALL_NOT_CONFIGURED' | 'NEW_SESSION_ALERT_CHANNEL_NOT_FOUND' | 'NEW_SESSION_ALERT_CHANNEL_JOIN_REQUIRED' | 'NEW_SESSION_ALERT_DELIVERY_FAILED' | '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' | 'GROUP_NOT_FOUND' | 'GROUP_NAME_CONFLICT' | 'GROUP_USER_NOT_FOUND' | 'SERVICE_ACCOUNT_ROLE_UNSUPPORTED' | 'SERVICE_ACCOUNT_ROLE_FORBIDDEN' | 'SERVICE_ACCOUNT_DEFAULT_PROTECTED' | 'API_KEY_NOT_FOUND' | 'API_KEY_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' | 'WEBHOOK_INGEST_NOT_CONFIGURED' | 'DATASET_AVAILABILITY_UNAVAILABLE' | 'CONNECTOR_NOT_FOUND' | 'PROCESSOR_NOT_FOUND' | 'PROCESSOR_NAME_CONFLICT' | 'PROCESSOR_CONFIG_STORAGE_UNAVAILABLE' | 'CERTIFICATE_NOT_FOUND' | 'CERTIFICATE_INVALID_CSR' | 'CERTIFICATE_LIMIT_REACHED' | 'ORGANIZATION_NOT_FOUND' | 'ORGANIZATION_LOGO_NOT_FOUND' | 'ORGANIZATION_LOGO_INVALID' | 'ORGANIZATION_LOGO_STORAGE_UNAVAILABLE' | 'USAGE_POLICY_TEMPLATE_NOT_FOUND' | 'USAGE_POLICY_NOT_FOUND' | 'USAGE_POLICY_CONFLICT' | 'USAGE_POLICY_RETIRED' | 'USAGE_BUDGET_ABOVE_CEILING' | 'USAGE_BACKFILL_TOO_LARGE' | 'USAGE_BACKFILL_CONTENDED' | 'USAGE_BACKFILL_UNAVAILABLE' | 'USAGE_EVENT_BACKFILL_TOO_LARGE' | 'USAGE_EVENT_BACKFILL_WINDOW_INVALID' | 'USAGE_ALERT_NOT_FOUND' | 'USAGE_ALERT_CONFLICT' | 'USAGE_ALERT_INVALID_THRESHOLD' | 'USAGE_ALERT_UNSUPPORTED_CHANNEL' | 'USAGE_ALERT_UNSUPPORTED_RECIPIENT' | 'ISSUED_CREDENTIAL_NOT_FOUND' | 'ISSUED_CREDENTIAL_TYPE_NOT_READY' | 'ISSUED_CREDENTIAL_ALREADY_REVOKED' | 'SESSION_WORKSPACE_FILE_NOT_FOUND' | 'SESSION_WORKSPACE_FILE_NOT_READABLE' | 'SESSION_WORKSPACE_INVALID_PATH' | 'AGENT_PROFILE_UNAVAILABLE' | 'INTERNAL_ERROR' | 'LEGAL_AGREEMENT_REQUIRED' | 'LEGAL_AGREEMENT_NOT_APPLICABLE' | 'LEGAL_AGREEMENT_VERSION_NOT_CURRENT' | 'LEGAL_AGREEMENT_DOCUMENT_UNAVAILABLE', 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

> 66 revisions in range; 9 not diffed.

- **2026-10-03** `609da234e5da`: 2 warning
  - added the new `WORKER_UPDATE_INVALID` enum value to the `failures/items/code` response property for the response status `201`
  - added the new `WORKER_UPDATE_INVALID` enum value to the `failures/items/code` response property for the response status `207`
- **2026-10-02** `53b6ebae31bc`: 2 warning
  - added the new `PRIVATE_CHAT_ARCHIVED` enum value to the `failures/items/code` response property for the response status `201`
  - added the new `PRIVATE_CHAT_ARCHIVED` enum value to the `failures/items/code` response property for the response status `207`
- **2026-10-01** `0fb124422604`: 6 warning
  - added the new `GROUP_NAME_CONFLICT` enum value to the `failures/items/code` response property for the response status `201`
  - added the new `GROUP_NAME_CONFLICT` enum value to the `failures/items/code` response property for the response status `207`
  - added the new `GROUP_NOT_FOUND` enum value to the `failures/items/code` response property for the response status `201`
  - added the new `GROUP_NOT_FOUND` enum value to the `failures/items/code` response property for the response status `207`
  - …2 more
- …earlier changes not shown

[Operation history](https://skmtc.dev/traversal/apis/traversal-api/changes/workers/bulk/post.md)

---

[API](https://skmtc.dev/traversal/apis/traversal-api.md) - [All operations](https://skmtc.dev/traversal/apis/traversal-api/llms.txt) - [OpenAPI document](https://skmtc.dev/traversal/apis/traversal-api/revisions/31e1a8b7cf89?raw)
