Build on SphereOne. API, webhooks and slash commands.

Post updates from your tools, read channels from your own code, upload files and create tasks. Every call is checked against the same rules people meet in the app.

Authentication

Send an API key in the Authorization header. Keys are shown once, when they are made, and only a hash is stored.

App keys

Start with so_bot_. An admin makes an app in Settings → Apps & API and gives it keys. The key posts as the app, with its own name and picture, and reaches only the channels and group chats the app was added to.

Personal keys

Start with so_usr_. Members make their own in Settings → Connections. A personal key acts as you, never does more than you can, always expires, and stops working the moment your account does.

curl https://app.sphereone.ai/v1/api/auth.test \
  -H "Authorization: Bearer $SPHEREONE_API_KEY"

Keep keys out of URLs, browsers and source control. A key in a query string is refused. Revoke a key you no longer trust from the same screen that made it.

Scopes

Each key carries only the permissions you tick. A call that needs another one is refused with missing_scope, which names it.

channels:readSee the channels the key can reach: names, topics and types.
channels:historyRead messages and threads in those channels.
conversations:readSee the group chats and direct messages the key can reach.
conversations:historyRead messages in those group chats and direct messages.
chat:writePost, edit and delete its own messages.
reactions:writeAdd and remove its own emoji reactions.
users:readSee people's names, photos, titles and time zones.
users:read.emailAlso see members' email addresses. Clients' addresses are never shared.
files:readDownload files the key can reach.
files:writeUpload files.
tasks:readRead tasks the key can reach.
tasks:writeCreate tasks.

Endpoints

Everything lives under https://app.sphereone.ai/v1. The OpenAPI document is at /api/openapi.json.

Auth

  • GET/api/auth.testCheck a key and see who it acts as—

Channels

  • GET/api/channelsChannels the key can seechannels:read
  • GET/api/channels/{id}One channelchannels:read
  • GET/api/channels/{id}/messagesMessages, newest firstchannels:history

Group chats and direct messages

  • GET/api/conversationsChats the key can seeconversations:read
  • GET/api/conversations/{id}/messagesMessages, newest firstconversations:history

Messages

  • POST/api/messagesPost a message or a thread replychat:write
  • GET/api/messages/{id}One messagechannels:history or conversations:history
  • GET/api/messages/{id}/repliesA thread, oldest firstchannels:history or conversations:history
  • PATCH/api/messages/{id}Edit a message the key postedchat:write
  • DELETE/api/messages/{id}Delete a message the key postedchat:write
  • POST/api/messages/{id}/reactionsReact with an emojireactions:write
  • DELETE/api/messages/{id}/reactions?emoji=…Remove the reactionreactions:write

People

  • GET/api/usersPeople the key can seeusers:read
  • GET/api/users/{id}One personusers:read

Files

  • POST/api/files/uploadsStart an uploadfiles:write
  • POST/api/filesFinish an uploadfiles:write
  • GET/api/files/{id}A file and a short-lived download linkfiles:read

Tasks

  • GET/api/tasksOpen tasks assigned to the key’s accounttasks:read
  • GET/api/tasks/{id}One tasktasks:read
  • POST/api/tasksCreate a task in a listtasks:write

Post a message

Text is Markdown. Add parentMessageId to reply in a thread and fileIds to attach files. Send an Idempotency-Key so a retry returns the message already posted instead of posting it twice. Messages arrive live, notify people you mention, and show up in search like anything typed in the app.

curl https://app.sphereone.ai/v1/api/messages \
  -H "Authorization: Bearer $SPHEREONE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: deploy-2026-10-13-001" \
  -d '{ "channelId": "CHANNEL_ID", "text": "Release **v2.4** is live" }'
{
  "ok": true,
  "data": {
    "id": "cm…",
    "channelId": "CHANNEL_ID",
    "author": { "id": "cm…", "displayName": "Deploy Bot", "isApp": true },
    "text": "Release **v2.4** is live",
    "createdAt": "2026-10-13T09:30:00.000Z",
    "idempotencyKey": "deploy-2026-10-13-001"
  }
}

Rate limits

120 calls a minute per key, of which up to 60 may change something.

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit you get HTTP 429 with the code rate_limited and a Retry-After header: wait that many seconds, then try again.

Pagination

Lists return up to 50 items by default and 200 at most. Pass the nextCursor you got back as cursor; null means the last page.

curl "https://app.sphereone.ai/v1/api/channels/CHANNEL_ID/messages?limit=100&cursor=NEXT_CURSOR" \
  -H "Authorization: Bearer $SPHEREONE_API_KEY"

Errors

Every response has ok. A failure carries a stable code to branch on, a sentence for people, and a request id to quote to support.

{
  "ok": false,
  "error": {
    "code": "missing_scope",
    "message": "This call needs the \"chat:write\" permission, and this key doesn't have it.",
    "neededScope": "chat:write",
    "requestId": "8f3c…"
  }
}
invalid_auth401No API key, or not a valid one.
key_revoked401The key was revoked.
key_expired401The key has passed its expiry date.
account_inactive401The person who owns this personal key can't use SphereOne any more.
app_disabled403An admin turned the app off.
workspace_suspended403The workspace is suspended or closed.
plan_upgrade_required403The workspace's plan doesn't include the API.
network_not_allowed403The workspace only allows access from its allowed networks, and this call came from somewhere else.
missing_scope403The key doesn't have the scope this call needs.
forbidden403The key can't reach this channel, chat, message, file or task.
not_found404It doesn't exist, or the key can't see it.
invalid_request400Something in the request is missing or not valid.
conflict409The request clashes with the current state.
quota_exceeded403A plan limit (storage, file size) would be passed.
payload_too_large413The request body is too large.
rate_limited429Too many calls. Wait for Retry-After seconds.
internal_error500Something went wrong on our side. Try again.

Files

Upload in three steps. The finished upload goes through the same checks as one from the app: your plan’s size and storage limits, and a malware scan.

# 1. Ask where to put the bytes
curl https://app.sphereone.ai/v1/api/files/uploads \
  -H "Authorization: Bearer $SPHEREONE_API_KEY" -H "Content-Type: application/json" \
  -d '{ "filename": "report.pdf", "contentType": "application/pdf", "sizeBytes": 48213 }'

# 2. PUT the file to the uploadUrl you got back
curl -X PUT "UPLOAD_URL" -H "Content-Type: application/pdf" --data-binary @report.pdf

# 3. Finish, then attach the file id to a message
curl https://app.sphereone.ai/v1/api/files \
  -H "Authorization: Bearer $SPHEREONE_API_KEY" -H "Content-Type: application/json" \
  -d '{ "uploadToken": "UPLOAD_TOKEN", "filename": "report.pdf", "contentType": "application/pdf", "sizeBytes": 48213 }'

Webhooks and slash commands

No code to host? An incoming webhook posts into one channel from any tool that can send JSON. Outgoing webhooks and slash commands call your server instead.

Incoming webhooks

An admin makes one in Settings → Apps & API. The URL is the credential, so treat it like a key.

Outgoing webhooks

SphereOne sends a signed POST for the events you choose: message.created, message.updated, message.deleted, reaction.added, reaction.removed, channel.created, channel.archived, member.joined, member.left, file.created, task.created, task.updated, task.completed. Each delivery is retried with backoff until your server answers 2xx.

curl -X POST https://app.sphereone.ai/v1/hooks/YOUR_WEBHOOK_TOKEN \
  -H "Content-Type: application/json" \
  -d '{ "text": "Build **#512** passed" }'

Check the signature

Every outgoing webhook and slash command request carries SphereOne-Signature: t=<unix seconds>,v1=<hmac>. The HMAC is SHA-256, keyed with the signing secret shown when you made the webhook, over t + "." + the raw body. Refuse anything more than 5 minutes old, and use SphereOne-Delivery to ignore a delivery you have already handled.

import { createHmac, timingSafeEqual } from 'node:crypto';

// header: the SphereOne-Signature header, e.g. "t=1760347800,v1=5f2c…"
// rawBody: the request body exactly as received, before any JSON parsing
export function verify(header, rawBody, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? '', 'hex');
  return given.length === expected.length && timingSafeEqual(given, expected);
}

Questions

About keys, plans and what the API can reach.

Which plans include the API?

The API and API keys come with Enterprise, and Business workspaces can add them with the Enterprise API add-on. Incoming and outgoing webhooks and slash commands come with Starter and up.

What is the difference between an app key and a personal key?

An app key acts as an app your admin created. It has its own name and picture, and it reaches only the channels and group chats it was added to. A personal key acts as you, for your own scripts, and can never do more than you can in SphereOne.

What happens to keys when someone leaves?

A personal key stops working the moment its owner is deactivated or removed. App keys belong to the workspace: an admin can turn the app off, revoke a key or delete the app at any time, and every call is checked against that straight away.

Can a key read every channel?

No. App keys see only where the app was added, and adding an app to a private channel takes someone who is in it. Personal keys see what their owner sees. Clients’ email addresses are never shared through the API.

Ready for an AI-native workspace?

Bring your team, your conversations and your history into one place.