API reference

Slack

9 endpoints, served at https://orbitwrite.com/api/v1. Each one lists the credentials it accepts, its parameters and every response it can give.

The browser URL that installs the Slack app#

GET/api/v1/integrations/slack/install-url

Returns the Slack OAuth URL a person must open to authorize the app — the one step of connecting that cannot happen over an API. A caller that already holds its own bot token should POST /integrations/slack instead and skip the browser entirely. Requires integration:connect.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Responses

200SlackInstallUrl

The install URL

Response attributes
urlstringRequired

Open this in a browser. On completion the workspace's install is written and the caller is returned to the app.

scopesstring[]Required

The bot scopes the install requests.

400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
501Error

Slack OAuth is not configured on this deployment

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/integrations/slack/install-url
curl -X GET "https://orbitwrite.com/api/v1/integrations/slack/install-url" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "url": "string",
  "scopes": [
    "string"
  ]
}

Read a workspace's Slack integration#

GET/api/v1/integrations/slack

Returns the Slack install (if any), the notification routes, the channels the bot can post to, and the workspace's tags for the optional tag filter. Requires integration:read, which every role holds. Channel listing failures degrade to an empty list.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Responses

200SlackIntegration

The workspace's Slack integration state

Response attributes
installationSlackInstallation | any | nullRequired
Child attributes
idstringRequired
slackTeamIdstringRequired
slackTeamNamestring | nullRequired
missingScopesstring[]Required

Bot scopes this app now requests that the install was not granted. Non-empty means the install predates them and features needing them (approving from Slack needs users:read and users:read.email; DMing reviewers their approval requests needs those plus im:write) won't work until Slack is reconnected. Empty when Slack reported no scopes at install.

routesSlackNotificationRoute[]Required
Child attributes
idstringRequired
channelIdstringRequired
channelNamestring | nullRequired
eventsstring[]Required
  • "post_published"
  • "post_failed"
  • "approval_requested"
  • "approval_decided"
  • "account_needs_reconnect"
  • "daily_digest"
tagIdstring | nullRequired
enabledbooleanRequired
channelsobject[]Required
Child attributes
idstringRequired
namestringRequired
isPrivatebooleanRequired
tagsobject[]Required
Child attributes
idstringRequired
namestringRequired
colorstringRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
GET /api/v1/integrations/slack
curl -X GET "https://orbitwrite.com/api/v1/integrations/slack" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "installation": {
    "id": "string",
    "slackTeamId": "string",
    "slackTeamName": "string",
    "missingScopes": [
      "string"
    ]
  },
  "routes": [
    {
      "id": "string",
      "channelId": "string",
      "channelName": "string",
      "events": [
        "post_published"
      ],
      "tagId": "string",
      "enabled": true
    }
  ],
  "channels": [
    {
      "id": "string",
      "name": "string",
      "isPrivate": true
    }
  ],
  "tags": [
    {
      "id": "string",
      "name": "string",
      "color": "string"
    }
  ]
}

Install Slack for a workspace from a bot token#

POST/api/v1/integrations/slack

Establishes the Slack install without a browser — for a CLI, an MCP tool, or anyone running their own Slack app. Re-running it refreshes the token in place rather than stacking a second install. The interactive alternative is the OAuth flow whose URL GET /integrations/slack/install-url returns. Requires integration:connect.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Body application/json

organizationIdstring

Required for session callers with no active organization. Optional for an API key, which is already scoped to one workspace — but a key naming a *different* workspace is refused with 403 rather than having the field ignored, so a caller can never believe it wrote somewhere it didn't.

At least 1 character.

botTokenstringRequired

A Slack bot token (xoxb-…). Which workspace it installs into is read from the token itself, so there is nothing else to supply — and nothing a caller could claim.

At least 1 character.

Responses

200ConnectSlackResult

Slack is installed for the workspace

Response attributes
okbooleanRequired
installationSlackInstallationRequired
Child attributes
idstringRequired
slackTeamIdstringRequired
slackTeamNamestring | nullRequired
missingScopesstring[]Required

Bot scopes this app now requests that the install was not granted. Non-empty means the install predates them and features needing them (approving from Slack needs users:read and users:read.email; DMing reviewers their approval requests needs those plus im:write) won't work until Slack is reconnected. Empty when Slack reported no scopes at install.

400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
502Error

Slack API unreachable while verifying the token

Response attributes
errorstringRequired
issuesany[]
POST /api/v1/integrations/slack
curl -X POST "https://orbitwrite.com/api/v1/integrations/slack" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "botToken": "string"
}'
Response
{
  "ok": true,
  "installation": {
    "id": "string",
    "slackTeamId": "string",
    "slackTeamName": "string",
    "missingScopes": [
      "string"
    ]
  }
}

Disconnect the workspace's Slack install#

DELETE/api/v1/integrations/slack

Removes the installation; its notification routes cascade. Requires integration:disconnect.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Responses

200SlackIntegrationMutationResult

The mutation was applied

Response attributes
okbooleanRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
DELETE /api/v1/integrations/slack
curl -X DELETE "https://orbitwrite.com/api/v1/integrations/slack" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "ok": true
}

Connect a Slack channel as a posting channel#

POST/api/v1/integrations/slack/channels

Adds a Slack channel to the workspace's channels, so posts can be published to it. Requires the workspace to have Slack installed, and account:connect. Re-connecting a channel that is already there refreshes it in place and keeps the schedule its owner chose.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Body application/json

organizationIdstring

Required for session callers with no active organization. Optional for an API key, which is already scoped to one workspace — but a key naming a *different* workspace is refused with 403 rather than having the field ignored, so a caller can never believe it wrote somewhere it didn't.

At least 1 character.

channelIdstringRequired

A Slack channel id (C…). Its name is read from Slack, not from this request. Public channels are joined by the bot so media posts work.

At least 1 character.

Responses

200ConnectSlackChannelResult

The channel is connected

Response attributes
okbooleanRequired
channelConnectedChannelRequired
Child attributes
idstringRequired
displayHandlestringRequired
createdbooleanRequired
restoredbooleanRequired
becamePrimarybooleanRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
502Error

Slack API unreachable while verifying the channel

Response attributes
errorstringRequired
issuesany[]
POST /api/v1/integrations/slack/channels
curl -X POST "https://orbitwrite.com/api/v1/integrations/slack/channels" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "channelId": "string"
}'
Response
{
  "ok": true,
  "channel": {
    "id": "string",
    "displayHandle": "string",
    "created": true,
    "restored": true,
    "becamePrimary": true
  }
}

Add a notification route#

POST/api/v1/integrations/slack/routes

Routes the chosen events (optionally filtered by tag) to a Slack channel. The channel is resolved server-side against what the bot can see, so the stored channel name is ours rather than the caller's. Requires integration:update.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Body application/json

organizationIdstring

Required for session callers with no active organization. Optional for an API key, which is already scoped to one workspace — but a key naming a *different* workspace is refused with 403 rather than having the field ignored, so a caller can never believe it wrote somewhere it didn't.

At least 1 character.

channelIdstringRequired

At least 1 character.

eventsstring[]Required

At least 1 item.

  • "post_published"
  • "post_failed"
  • "approval_requested"
  • "approval_decided"
  • "account_needs_reconnect"
  • "daily_digest"
tagIdstring | null

Narrow the rule to posts carrying this tag. Null (or omitted) means all posts; a tag from another workspace is rejected.

Responses

200SlackNotificationRouteResult

The route was added

Response attributes
okbooleanRequired
routeSlackNotificationRouteRequired
Child attributes
idstringRequired
channelIdstringRequired
channelNamestring | nullRequired
eventsstring[]Required
  • "post_published"
  • "post_failed"
  • "approval_requested"
  • "approval_decided"
  • "account_needs_reconnect"
  • "daily_digest"
tagIdstring | nullRequired
enabledbooleanRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
502Error

Slack API unreachable while verifying the channel

Response attributes
errorstringRequired
issuesany[]
POST /api/v1/integrations/slack/routes
curl -X POST "https://orbitwrite.com/api/v1/integrations/slack/routes" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "channelId": "string",
  "events": [
    "post_published"
  ]
}'
Response
{
  "ok": true,
  "route": {
    "id": "string",
    "channelId": "string",
    "channelName": "string",
    "events": [
      "post_published"
    ],
    "tagId": "string",
    "enabled": true
  }
}

Enable or pause a notification route#

POST/api/v1/integrations/slack/routes/{routeId}/toggle

Requires integration:update.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Path parameters

routeIdstringRequired

At least 1 character.

Body application/json

organizationIdstring

Required for session callers with no active organization. Optional for an API key, which is already scoped to one workspace — but a key naming a *different* workspace is refused with 403 rather than having the field ignored, so a caller can never believe it wrote somewhere it didn't.

At least 1 character.

enabledbooleanRequired

Responses

200SlackIntegrationMutationResult

The mutation was applied

Response attributes
okbooleanRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
POST /api/v1/integrations/slack/routes/{routeId}/toggle
curl -X POST "https://orbitwrite.com/api/v1/integrations/slack/routes/{routeId}/toggle" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": true
}'
Response
{
  "ok": true
}

Remove a notification route#

DELETE/api/v1/integrations/slack/routes/{routeId}

Requires integration:update.

Authentication

An API key or OAuth access token as a bearer token, or a signed-in session.

Path parameters

routeIdstringRequired

At least 1 character.

Query parameters

organizationIdstring

Session callers only — an API key already determines the workspace.

At least 1 character.

Responses

200SlackIntegrationMutationResult

The mutation was applied

Response attributes
okbooleanRequired
400Error

Invalid request

Response attributes
errorstringRequired
issuesany[]
401Error

Not signed in

Response attributes
errorstringRequired
issuesany[]
403Error

Not a member of the organization

Response attributes
errorstringRequired
issuesany[]
404Error

The resource does not exist

Response attributes
errorstringRequired
issuesany[]
DELETE /api/v1/integrations/slack/routes/{routeId}
curl -X DELETE "https://orbitwrite.com/api/v1/integrations/slack/routes/{routeId}" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
{
  "ok": true
}

Slack interactive-message callback#

POST/api/v1/integrations/slack/interactivity

Slack posts here when someone clicks Approve or Reject on an approval message. The body is application/x-www-form-urlencoded with a single payload field carrying Slack's JSON interaction object. Not for general callers: the request is authenticated by Slack’s own X-Slack-Signature (HMAC over the raw body, five-minute replay window) rather than by a session or API key, and the URL is registered in the Slack app’s configuration. Decisions run through the same approvalsService.decide the app’s own approval endpoint uses.

Authentication

None. This endpoint is public.

Responses

200string

Acknowledged. The decision is processed out of band and reported through Slack's response_url.

401string

The Slack signature did not verify

POST /api/v1/integrations/slack/interactivity
curl -X POST "https://orbitwrite.com/api/v1/integrations/slack/interactivity" \
  -H "Authorization: Bearer $ORBITWRITE_API_KEY"
Response
"string"