Finish installing an agent's Slack app with what Slack returned
The console's callback page sends the state and the code (or Slack's error) from Slack's redirect, with the agent it is for. The state is spent first, whatever happens next: unknown, spent, expired, or issued to another caller, workspace, agent or app answers 409 SLACK_INSTALL_STATE_INVALID with error.details.reason_code. An error from Slack (e.g. access_denied) answers 409 SLACK_INSTALL_DENIED. The code is exchanged with oauth.v2.access; unless the answer names the agent's app, the workspace's Slack team, no enterprise install and every bot scope, nothing is stored (409 SLACK_INSTALL_REJECTED). Then the bot token is sealed under the app (never returned), the install is verified with the token itself (auth.test and bots.info), the agent's identity is linked, and the steps after an install run; finalized: false says those failed and the install stands. Installing again with a new link replaces the bot token and keeps the one install. Every answer carries Referrer-Policy: no-referrer. Owners, admins and the agent's admins from a signed-in console; API keys are refused. A new identity writes a link_agent_identity receipt.
Auth: cookie session only; no scope. Idempotency-Key: ignored.
cookieSession__Host-werft_session<token>For the web console: the browser session cookie set by POST /v1/auth/login. It holds the scopes of the user's workspace role (owner: all; admin and member: all but sessions:recover). Requests other than GET must also send X-Requested-With: werft-web.
X-Requested-With?"werft-web"Required with a cookie session: the literal werft-web.
"werft-web"application/json- body
agent_id*string^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$uuidstate*string^[A-Za-z0-9_-]{1,512}$code?string^[A-Za-z0-9._-]{1,2048}$error?string^[a-z_]{1,64}$The app, its verified install, the agent's identity and whether the steps after the install ran.
application/json- response
slack_app*installation*agent_identity*receipt_id*|nullfinalized*booleanwarnings*array<>curl -X POST 'https://example.com/v1/slack/install/complete' \ -H 'Content-Type: application/json' \ -d '{ "agent_id": "8d2c4f1a-6b3e-4a9d-b7c5-2e1f0a9b8c7d", "state": "state-from-the-redirect", "code": "code-from-the-redirect"}'{ "slack_app": { "id": "6a1d3e5f-2b4c-4d8e-9f0a-1b2c3d4e5f60", "kind": "agent", "agent_id": "8d2c4f1a-6b3e-4a9d-b7c5-2e1f0a9b8c7d", "state": "created", "app_id": "A0B1C2D3E4F", "client_id": "1234567890.9876543210", "redirect_url": "https://console.example.ts.net/agents/8d2c4f1a-6b3e-4a9d-b7c5-2e1f0a9b8c7d/slack/callback", "created_by_token_user": "U0ISSUER", "created_at": "2026-10-23T09:00:00Z", "updated_at": "2026-10-23T09:00:00Z", "secrets": { "slack_signing_secret": { "configured": true, "last4": "c3d4", "updated_at": "2026-10-23T09:00:00Z", "updated_by": null }, "slack_client_secret": { "configured": true, "last4": "e5f6", "updated_at": "2026-10-23T09:00:00Z", "updated_by": null }, "slack_bot_token": { "configured": true, "last4": "a1b2", "updated_at": "2026-10-24T09:05:00Z", "updated_by": null } } }, "installation": { "id": "3c5e7a9b-1d2f-4a6b-8c0d-2e4f6a8b0c1d", "app_id": "A0B1C2D3E4F", "team_id": "T0A1B2C3D", "bot_user_id": "U0B1C2D3E", "status": "verified" }, "agent_identity": { "id": "5e7a9c1b-3d4f-4b6c-8d0e-4f6a8b0c2d3e", "agent_id": "8d2c4f1a-6b3e-4a9d-b7c5-2e1f0a9b8c7d", "provider": "slack_bot", "app_id": "A0B1C2D3E4F", "team_id": "T0A1B2C3D", "bot_user_id": "U0B1C2D3E", "credential_ref": "slack_app:6a1d3e5f-2b4c-4d8e-9f0a-1b2c3d4e5f60/slack_bot_token", "status": "active", "revision": 1, "created_by": "7a9c1e3b-5d6f-4c8d-9e0f-6a8b0c2d4e5f", "created_at": "2026-10-24T09:05:00Z", "revoked_at": null }, "receipt_id": "9c1e3a5b-7d8f-4e0a-8b1c-8b0c2d4e6f7a", "finalized": true, "warnings": []}