API reference
9 endpoints, served at https://orbitwrite.com/api/v1. Each one lists the credentials it accepts, its parameters and every response it can give.
/api/v1/integrations/slack/install-urlGET/api/v1/integrations/slackPOST/api/v1/integrations/slackDEL/api/v1/integrations/slackPOST/api/v1/integrations/slack/channelsPOST/api/v1/integrations/slack/routesPOST/api/v1/integrations/slack/routes/{routeId}/toggleDEL/api/v1/integrations/slack/routes/{routeId}POST/api/v1/integrations/slack/interactivityReturns 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.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
200SlackInstallUrlThe install URL
curl -X GET "https://orbitwrite.com/api/v1/integrations/slack/install-url" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY"{
"url": "string",
"scopes": [
"string"
]
}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.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
200SlackIntegrationThe workspace's Slack integration state
installationSlackInstallation | any | nullRequiredidstringRequiredslackTeamIdstringRequiredslackTeamNamestring | nullRequiredmissingScopesstring[]RequiredBot 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[]Requiredcurl -X GET "https://orbitwrite.com/api/v1/integrations/slack" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY"{
"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"
}
]
}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.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringRequired 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.
botTokenstringRequiredA 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.
200ConnectSlackResultSlack is installed for the workspace
okbooleanRequiredinstallationSlackInstallationRequiredidstringRequiredslackTeamIdstringRequiredslackTeamNamestring | nullRequiredmissingScopesstring[]RequiredBot 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.
curl -X POST "https://orbitwrite.com/api/v1/integrations/slack" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"botToken": "string"
}'{
"ok": true,
"installation": {
"id": "string",
"slackTeamId": "string",
"slackTeamName": "string",
"missingScopes": [
"string"
]
}
}Removes the installation; its notification routes cascade. Requires integration:disconnect.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
200SlackIntegrationMutationResultThe mutation was applied
okbooleanRequiredcurl -X DELETE "https://orbitwrite.com/api/v1/integrations/slack" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY"{
"ok": true
}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.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringRequired 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.
channelIdstringRequiredA 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.
200ConnectSlackChannelResultThe channel is connected
okbooleanRequiredchannelConnectedChannelRequiredidstringRequireddisplayHandlestringRequiredcreatedbooleanRequiredrestoredbooleanRequiredbecamePrimarybooleanRequiredcurl -X POST "https://orbitwrite.com/api/v1/integrations/slack/channels" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channelId": "string"
}'{
"ok": true,
"channel": {
"id": "string",
"displayHandle": "string",
"created": true,
"restored": true,
"becamePrimary": true
}
}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.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringRequired 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.
channelIdstringRequiredAt least 1 character.
eventsstring[]RequiredAt least 1 item.
"post_published""post_failed""approval_requested""approval_decided""account_needs_reconnect""daily_digest"tagIdstring | nullNarrow the rule to posts carrying this tag. Null (or omitted) means all posts; a tag from another workspace is rejected.
200SlackNotificationRouteResultThe route was added
okbooleanRequiredcurl -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"
]
}'{
"ok": true,
"route": {
"id": "string",
"channelId": "string",
"channelName": "string",
"events": [
"post_published"
],
"tagId": "string",
"enabled": true
}
}Requires integration:update.
An API key or OAuth access token as a bearer token, or a signed-in session.
routeIdstringRequiredAt least 1 character.
organizationIdstringRequired 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.
enabledbooleanRequired200SlackIntegrationMutationResultThe mutation was applied
okbooleanRequiredcurl -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
}'{
"ok": true
}Requires integration:update.
An API key or OAuth access token as a bearer token, or a signed-in session.
routeIdstringRequiredAt least 1 character.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
200SlackIntegrationMutationResultThe mutation was applied
okbooleanRequiredcurl -X DELETE "https://orbitwrite.com/api/v1/integrations/slack/routes/{routeId}" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY"{
"ok": true
}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.
None. This endpoint is public.
200stringAcknowledged. The decision is processed out of band and reported through Slack's response_url.
401stringThe Slack signature did not verify
curl -X POST "https://orbitwrite.com/api/v1/integrations/slack/interactivity" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY""string"