allternit-api) can also start one. These routes are what the phone’s Start new sheet calls. User docs: Your bot’s phone, Messaging channels.
All routes sit under /api/v1 and need the signed-in user. The bot must be yours, and switched on for a connection of that channel (Agent Gateway → Messaging). Nothing here needs new environment variables: a channel that isn’t connected answers 409 not_connected, and a connection whose transport has no credentials answers 503 <provider>_not_configured.
List who a bot can start with
provider is telegram, slack, discord, teams, whatsapp, whatsapp-personal, sms (alias phone) or email. Targets come from what the runtime already knows: conversations stored for the bot’s connections, plus (Slack, when the runtime holds a bot token) the channels the bot has joined. canStartWith lists the kinds that can be listed (user, group, channel) and the ways to type one in (username, phone, email). notes is the platform’s rule in one line. A bot with no connection gets connected: false and an empty list, not an error. WhatsApp lists only people inside the 24-hour window.
Start one
target takes kind plus one of id, username, email or phone. subject is for email only. attachments is an optional list of files, covered under Attachments. On success:
state is sent (200), or unconfirmed / pending_approval (202, still on its way). existing: true means the person already had a conversation with this bot and the message went into that thread.
The started conversation is a normal Allternit thread with a channel binding. The binding uses the same conversation key the platform’s inbound events use, so the person’s reply lands in the same thread. The bot’s first message is stored as its outbound message, so it shows in the main thread list and in Messages. It goes out through the same send path as any reply, so the bot’s channel tool policy (a send rule set to ask returns 428 approval_required with the threadId and approvalId), the delivery log and the channel.message.* events all apply.
A refused start never leaves an empty thread when the platform’s rule can be checked first (everything in the table except a refusal from the platform itself). Errors are { "error": "<code>", "message": "…" }:
Rules per channel
Other codes:
unknown_provider (404), bot_not_found (404), not_connected (409), text_required (400), invalid_target (400), no_consent (403), conversation_belongs_to_other_bot (409), channel_policy (403), read_only (403), channel_rejected (502, the platform said no; the message says why).
Discord direct messages
The bot token of the shared Allternit Discord app lives only in cloud-api, so a runtime asks the cloud to open the DM. The route is authenticated likePOST /api/v1/channels/discord/send (the runtime-device token):
userIdis required: a Discord snowflake.guildIdis optional; without it any server the caller installed the app in counts. AguildIdthat isn’t the caller’s is ignored, not trusted.- The cloud only messages a person who is a member of one of the caller’s installed servers (
GET /guilds/{id}/members/{user}), then callsPOST /users/@me/channelsandPOST /channels/{id}/messages. A DM can’t wear a webhook’s name or avatar, so withbotNamethe message starts with the bot’s name in bold. - Without
textthe cloud only opens the channel. The start route does this first, so the conversation is bound asdiscord:<dm channel id>and a reply from the person lands in the same thread.
Discord docs: Create DM, Get Guild Member, Create Message.
Attachments
attachments on a start (and on POST /api/v1/gateway/threads/{threadId}/channel-send for replies) is a list of files, each { "filename", "mimeType", "dataBase64" }. The file names name, mime/type and contentBase64 are accepted too. Files are checked before any thread or send exists.
Every channel: 10 MB per file, 20 MB per message. Refusals:
400 attachments_unsupported (the channel takes none), 400 invalid_attachment (no data, empty or bad base64), 413 attachment_too_large (size or count). Start and channel-send accept request bodies up to 30 MB for this. The cloud’s Slack send route takes the same files list ({ "channel", "text", "threadTs", "files": [...] }) and enforces the same caps. If the text posts but the upload fails, the send is reported as unconfirmed rather than failed, so it isn’t posted twice.
Files in the thread
Files are kept so the thread can offer them as downloads, on Desktop, the ai.allternit.com phone layout and the m.allternit.com app alike. When a bot sends files, and when someone sends a file in Telegram or Slack, the runtime stores each one in the user’s files at the cloud (Files API, plan limits apply) and the thread message event carries:size is bytes. url is the file’s permanent link, so the chip is a real download link and an old message never has an expired one (deleting the file in Settings > Files kills the link). A file that couldn’t be kept (error: too_large, storage-quota-exceeded, file-too-large, not_paired, slack_reauthorize_required, or a platform refusal) shows by name with the reason, and the message is never blocked by it. The older attachments: [name] list stays on the event.
Inbound, per platform:
A message that is only a file (no text) arrives as
[file: name] so it still reaches a thread and the bot sees that something came. Up to 5 files per message; a platform retry of a message already logged is not downloaded again.
Teams proactive messages
Not built. Starting a Teams chat with someone the bot has no conversation reference for needs the two-tenant proactive-messaging spike, which needs a second Microsoft tenant to test against. Until then the start route answers409 no_conversation_reference: “Teams only lets the bot message a conversation it already has a reference for. Have the person install or message the bot in Teams first.”
Vendor bots
A vendor bot’spost_message tool (see Vendor bots use your phone) calls this same start path with the directing bot, so every rule above, the channel policy and approvals apply. A refusal comes back as one plain sentence (for example “Telegram bots can only message a person who has messaged the bot first.”), and a send that needs approval says “That needs the owner’s approval in Allternit first.”
Mail threads
inbox: mail the bot received, one entry per sender and subject (withoutRe:), newest first.unreadis true until the bot’s reply was sent or is waiting for approval. Mail dropped by the loop guards is left out.idis the bot’s thread when one exists.needs_ok: outbound mail waiting for approval.idis the review thread; approve or reject it withPOST /api/factory/mail/decide, or withgizzi orchestration mail decide.sent: mail that went out.
folder is 400 invalid_folder.
Source
cmd/allternit-api/src/channel_start.rs (start, plus VendorStarter for vendor bots), channel_files.rs (attachment parsing and caps), channel_discord_dm.rs (DM client). Cloud: cmd/allternit-cloud-api/src/routes/discord_app.rs (/dm) and slack_app.rs (/send with files). The first message uses channel_gateway::send; email uses agent_email_routes::send_email_with_files.