kappelas / kappelas-sdk-php
Official PHP SDK for the Kappela messaging platform β v0.2.2
Requires
- php: >=8.1
- guzzlehttp/guzzle: ^7.0
- textalk/websocket: ^1.6
Requires (Dev)
- phpunit/phpunit: ^10.0
README
Official PHP SDK for the Kappela messaging platform.
Build bots and personal automations with a clean, typed API.
Table of Contents
- Prerequisites
- Install
- Quick start
- Pausing automations
- PHP type hints & autocompletion
- Events β WebSocket vs Webhook
- bot->reply()
- Message fields
- CallbackQuery fields
- API reference
- Keyboards
- Text formatting
- Error handling
- File input
Prerequisites
- PHP 8.1+
- Composer
Install
composer require kappelas/kappelas-sdk-php
Quick start
<?php require 'vendor/autoload.php'; use Kappelas\KappelaBot; use Kappelas\Types\Message; $bot = new KappelaBot('YOUR_BOT_TOKEN'); $bot->onMessage(function (Message $msg) use ($bot) { if ($msg->text === '/start') { $bot->messages->send([ 'chat_id' => $msg->chatId, 'text' => 'Hello! π', ]); } }); $bot->start(); // blocks β WebSocket loop
For a webhook setup, call $bot->handleWebhook($payload) instead of $bot->start():
$payload = json_decode(file_get_contents('php://input'), true); $bot->handleWebhook($payload);
Personal automation
Authenticate as yourself with a personal API key (sk_...). KappelaUser exposes the same resources as KappelaBot β $me->messages, $me->chats (including member management and invite links), $me->communities, $me->profile, and $me->reply(). In addition, KappelaUser has $me->stories (user-only). Collections and Hugging Face credentials are bot-only.
$me = new Kappelas\KappelaUser('sk_...'); $me->reply($msg, 'Got it! π'); $me->communities->create(['name' => 'Devs', 'requires_approval' => true]); // User-only: stories $me->stories->create(['type' => 'text', 'caption' => 'Hello π']);
Pausing automations
Pausing your personal automation makes your account stop receiving incoming messages over /v1/me, so an AI auto-responder is never triggered, and any send is rejected with AUTOMATIONS_PAUSED β until you resume. Pausing a bot makes it stop receiving incoming messages (no WebSocket push, no webhook) and rejects sends with BOT_PAUSED until resumed. This is useful when the human owner wants to take over and stop the AI.
use Kappelas\KappelaUser; use Kappelas\KappelaBot; $me = new Kappelas\KappelaUser('sk_...'); $me->pauseAutomations(); // β ['automations_paused' => true] $me->resumeAutomations(); // β ['automations_paused' => false] $me->getAutomationStatus(); // β ['automations_paused' => bool] $bot = new Kappelas\KappelaBot('YOUR_BOT_TOKEN'); $bot->pause(); // β ['paused' => true] $bot->resume(); // β ['paused' => false] $bot->getStatus(); // β ['paused' => bool]
To pause only in ONE conversation (e.g. you take over a single chat while the AI keeps handling the rest):
// Personal automation, scoped to one chat $me->pauseAutomationInChat($chatId); // β ['done' => true] $me->resumeAutomationInChat($chatId); // β ['done' => true] // Bot, scoped to one chat $bot->pauseInChat($chatId); // β ['done' => true] $bot->resumeInChat($chatId); // β ['done' => true]
PHP type hints & autocompletion
Every method has full PHPDoc with typed @param shapes and @return types. IDEs (PhpStorm, VS Code + Intelephense) provide autocompletion on all result properties:
$result = $bot->messages->send(['chat_id' => 123, 'text' => 'Hi']); $result->messageId; // int $result->createdAt; // int|null
Events β WebSocket vs Webhook
| Feature | WebSocket (start()) |
Webhook (handleWebhook()) |
|---|---|---|
| Setup | No HTTPS required | Requires public HTTPS URL |
| Connection | Persistent TCP | Stateless HTTP |
| Use case | Development, VPS bots | Serverless, shared hosting |
WebSocket:
$bot->onMessage(fn(Message $msg) => ...); $bot->onCallbackQuery(fn(CallbackQuery $cb) => ...); $bot->onConnected(fn() => ...); $bot->onDisconnected(fn(int $code, string $reason) => ...); $bot->onError(fn(Throwable $e) => ...); $bot->start();
Webhook:
// In your HTTP handler: $payload = json_decode(file_get_contents('php://input'), true); $bot->handleWebhook($payload); // Same onMessage / onCallbackQuery handlers are called synchronously.
bot->reply()
Reply to a received message in one call β reply_to_id is injected automatically:
$bot->onMessage(function (Message $msg) use ($bot) { $bot->reply($msg, 'β©οΈ Got your message!'); });
With a keyboard:
$bot->reply($msg, 'Choose an option:', [ 'reply_markup' => [ 'inline_keyboard' => [[ ['text' => 'β Yes', 'callback_data' => 'yes'], ['text' => 'β No', 'callback_data' => 'no'], ]], ], ]);
Message fields
$msg->id // int β message ID $msg->chatId // int β chat ID $msg->senderId // ?string $msg->type // ?string β 'text'|'image'|'video'|'audio'|'document'|... $msg->text // ?string $msg->mediaId // ?string $msg->extraData // mixed β inline keyboard definition when received $msg->status // string β 'sent'|'delivered'|'read' $msg->editedAt // ?int β Unix timestamp $msg->deletedAt // ?int $msg->createdAt // int β Unix timestamp $msg->replyToId // ?int $msg->replyToSnapshot // ?ReplySnapshot $msg->mentions // array $msg->forwardedFrom // mixed $msg->expiresAt // ?int $msg->senderName // ?string $msg->senderUsername // ?string $msg->senderAvatarUrl // ?string $msg->clientMsgId // ?string $msg->width // ?int β media width in pixels $msg->height // ?int β media height in pixels $msg->chatType // ?string β 'private'|'group'|'channel'
CallbackQuery fields
$cb->chatId // int $cb->senderId // string $cb->callbackData // string $cb->senderName // ?string $cb->senderUsername // ?string $cb->sentAt // ?int
API reference
messages
Recipient β
chat_idoruser_id. Every send / edit / delete / typing method accepts eitherchat_id(int) oruser_id(string UUID). Withuser_idthe message is routed to your 1-to-1 private chat with that user β a bot requires the conversation to already exist (FORBIDDENotherwise); a user creates it automatically (find-or-create). Foredit/deletethe conversation must exist.$bot->messages->send(['user_id' => 'f19f2127-β¦', 'text' => 'Hi']); $me->messages->sendPhoto(['user_id' => $cb->senderId, 'file' => $file]);
// Send text β by chat_id or user_id $bot->messages->send([ 'chat_id' => 123, 'text' => 'Hello!', 'reply_markup' => [...], // optional keyboard 'reply_to_id' => 456, // optional β reply to message ID 'delete_previous' => true, // optional ]); // β SendResult { messageId: int, createdAt: ?int } // Action button β foot-of-bubble copy / link / join button (takes precedence over reply_markup). // type β copy_text | external_link | internal_link | join $bot->messages->send([ 'chat_id' => 123, 'text' => 'Your code is 837192', 'action_button' => ['label' => 'Copy code', 'type' => 'copy_text', 'value' => '837192'], ]); // Send media $bot->messages->sendPhoto([ 'chat_id' => 123, 'file' => ['data' => $bytes, 'filename' => 'photo.jpg', 'content_type' => 'image/jpeg'], 'caption' => 'Caption text', 'reply_to_id' => 456, 'delete_previous' => true, 'reply_markup' => [...], ]); // sendVideo(), sendDocument(), sendAudio() β same signature // β SendMediaResult { messageId: int, createdAt: ?int, mediaId: string } // Carousel $bot->messages->sendCarousel([ 'chat_id' => 123, 'text' => 'Our products:', 'carousel' => [ ['id' => 'p1', 'title' => 'Product A', 'subtitle' => '$9.99', 'button_text' => 'View'], ], 'quick_reply_buttons' => ['See more', ['text' => 'β Cancel', 'callback_data' => 'cancel']], 'reply_to_id' => 456, ]); // β SendCarouselResult { messageId: int, createdAt: ?int, type: 'carousel' } // Typing indicator $bot->messages->sendTyping(['chat_id' => 123]); $bot->messages->sendTyping(['chat_id' => 123, 'is_typing' => false]); // β TypingResult { typing: bool } // Edit β texte ET clavier ensemble (ex. un menu qui se coche au clic) $bot->messages->edit([ 'chat_id' => 123, 'message_id' => 456, 'new_text' => 'Tu as choisi : β Oui', 'new_extra_data' => [ 'inline_keyboard' => [[ ['text' => 'β Oui β', 'callback_data' => 'yes'], ['text' => 'β Non', 'callback_data' => 'no'], ]], ], ]); // β EditMessageResult { edited: bool, messageId: int } // Clavier seul (on omet 'new_text' β le texte est conservΓ©) : $bot->messages->edit([ 'chat_id' => 123, 'message_id' => 456, 'new_extra_data' => ['inline_keyboard' => [[['text' => 'Done β ', 'callback_data' => 'done']]]], ]); // - new_text + new_extra_data β change le texte ET le clavier // - new_extra_data seul (sans new_text) β change le clavier, garde le texte // - new_text seul β change le texte, laisse le clavier // Delete $bot->messages->delete(['chat_id' => 123, 'message_id' => 456]); // β DeleteResult { deleted: bool } // Resolve a media_id β signed download URL + metadata $info = $bot->messages->getFile($msg->mediaId); // β FileInfo { mediaId, url, filename, contentType, sizeBytes, expiresIn } // Or download the raw bytes directly (e.g. a received voice note to transcribe) $bytes = $bot->messages->downloadFile($msg->mediaId); // binary string
delete_previous
When delete_previous: true, the bot's last message in the chat is deleted before sending the new one. Useful for menus that should replace themselves:
// First send $bot->messages->send(['chat_id' => $chatId, 'text' => 'Step 1']); // Next send β the "Step 1" message is deleted first $bot->messages->send([ 'chat_id' => $chatId, 'text' => 'Step 2', 'delete_previous' => true, ]);
chats
// Paginated list $result = $bot->chats->list(['limit' => 20, 'offset' => 0]); // β ChatsResult { chats: Chat[], hasMore: bool } // Auto-pagination β return false from $fn to stop early $bot->chats->iterate(50, function (Chat $chat): bool { echo $chat->title . "\n"; return true; // continue });
Chat fields:
$chat->chatId // int $chat->id // int $chat->type // 'private'|'group'|'channel' $chat->title // ?string $chat->participants // Participant[] $chat->lastMessageAt // mixed $chat->createdAt // string $chat->createdBy // string $chat->isPinned // bool $chat->isPremium // bool $chat->isPublic // bool $chat->onlyAdminsCanWrite // bool $chat->labels // array $chat->description // ?string $chat->avatarUrl // ?string
Participant fields:
$p->id // string $p->nom // string $p->isBot // bool $p->isPremium // bool $p->avatarUrl // ?string $p->role // ?string β 'member'|'admin' (null in private chats)
Groups & channels
Receiving group messages
Group messages arrive via the same onMessage handler:
$bot->onMessage(function (Message $msg) use ($bot) { if ($msg->chatType === 'group') { // handle group message } });
Replying in a group
$bot->reply($msg, 'Reply to group message');
Getting member IDs
$admins = $bot->chats->getAdministrators(['chat_id' => $groupId]); foreach ($admins->admins as $admin) { echo $admin->userId . ' β ' . $admin->role . "\n"; }
Detecting conversation type
$bot->onMessage(function (Message $msg) use ($bot) { $context = match($msg->chatType) { 'private' => 'private chat', 'group' => 'group', 'channel' => 'channel', default => 'unknown', }; $bot->messages->send(['chat_id' => $msg->chatId, 'text' => "You're in a $context"]); });
Full group bot example
<?php require 'vendor/autoload.php'; use Kappelas\KappelaBot; use Kappelas\Types\Message; use Kappelas\Types\CallbackQuery; $bot = new KappelaBot('YOUR_BOT_TOKEN'); // Get groups the bot belongs to $groups = $bot->chats->getMyGroups(); foreach ($groups->groups as $g) { echo "Group: {$g->title} ({$g->type}) β bot role: {$g->botRole}\n"; } $bot->onMessage(function (Message $msg) use ($bot) { if ($msg->chatType !== 'group') return; if ($msg->text === '/members') { $admins = $bot->chats->getAdministrators(['chat_id' => $msg->chatId]); $list = implode(', ', array_map(fn($a) => $a->userId, $admins->admins)); $bot->reply($msg, "Admins: $list"); } }); $bot->onCallbackQuery(function (CallbackQuery $cb) use ($bot) { $bot->messages->send([ 'chat_id' => $cb->chatId, 'text' => 'You clicked: ' . $cb->callbackData, ]); }); $bot->start();
Chat member management
Admin-only operations.
// Add a member $bot->chats->addMember(['chat_id' => 123, 'user_id' => 'abc456']); // β AddChatMemberResult { description: string } // Ban a member $bot->chats->banMember(['chat_id' => 123, 'user_id' => 'abc456']); // β BanChatMemberResult { description: string } // Leave a chat $bot->chats->leaveChat(['chat_id' => 123]); // β LeaveChatResult { description: string } // Promote / demote $bot->chats->promoteMember(['chat_id' => 123, 'user_id' => 'abc456', 'role' => 'admin']); $bot->chats->promoteMember(['chat_id' => 123, 'user_id' => 'abc456', 'role' => 'member']); // β PromoteChatMemberResult { userId: string, role: string } // Get all admins $result = $bot->chats->getAdministrators(['chat_id' => 123]); // β GetChatAdministratorsResult { admins: ChatMemberInfo[] } // Get one member $info = $bot->chats->getMember(['chat_id' => 123, 'user_id' => 'abc456']); // β ChatMemberInfo { userId: string, role: string }
Invite links (admin only)
// Create a permanent link (no limit) $link = $bot->chats->createInviteLink(['chat_id' => 123]); // Create with options $link = $bot->chats->createInviteLink([ 'chat_id' => 123, 'max_uses' => 10, 'expires_in' => 86400, // seconds ]); // Single-use shorthand (max_uses=1) $link = $bot->chats->createSingleUseInviteLink(['chat_id' => 123]); // β ChatInviteLink { code, url, maxUses, useCount, expiresAt, createdAt } // List active links $result = $bot->chats->getInviteLinks(['chat_id' => 123]); // β GetChatInviteLinksResult { inviteLinks: ChatInviteLink[] } // Revoke a link $bot->chats->revokeInviteLink(['chat_id' => 123, 'code' => $link->code]); // β RevokeChatInviteLinkResult { revoked: bool, code: string }
getMyGroups
$result = $bot->chats->getMyGroups(); // β GetMyGroupsResult { groups: BotGroupEntry[] } foreach ($result->groups as $group) { echo "{$group->title} β {$group->type} β {$group->participantCount} members β bot: {$group->botRole}\n"; }
BotGroupEntry fields: chatId, type, title, participantCount, botRole
communities
Manage communities a bot belongs to: CRUD, members & roles, invite links, join
requests, and group requests. A bot can only administer a community where it is an
admin. Note: the community role (member/admin) is distinct from a group role.
use Kappelas\KappelaBot; $bot = new KappelaBot('YOUR_BOT_TOKEN'); // --- CRUD --- $c = $bot->communities->create(['name' => 'Devs', 'description' => 'Notre commu', 'requires_approval' => true]); $all = $bot->communities->list(); // Community[] (each with ->role) $admin = $bot->communities->listAdmin(); // only those where the bot is admin $one = $bot->communities->get(['community_id' => $c->id]); // CommunityDetail (with members) $bot->communities->update(['community_id' => $c->id, 'description' => 'Nouvelle desc']); // only sent fields change $bot->communities->delete(['community_id' => $c->id]); $bot->communities->join(['community_id' => 42]); // ->pending === true if approval required // --- Members & roles --- // To make someone (person OR bot) admin: add them as member, then promote. $bot->communities->addMember(['community_id' => $c->id, 'user_id' => 'uuid', 'role' => 'member']); $bot->communities->promoteMember(['community_id' => $c->id, 'user_id' => 'uuid', 'role' => 'admin']); $bot->communities->banMember(['community_id' => $c->id, 'user_id' => 'uuid']); // remove a member $bot->communities->leave(['community_id' => $c->id]); // --- Invite links --- $inv = $bot->communities->createInviteLink(['community_id' => $c->id, 'max_uses' => 10, 'expires_in' => '24h']); $list = $bot->communities->getInviteLinks(['community_id' => $c->id]); // CommunityInvite[] $bot->communities->revokeInviteLink(['community_id' => $c->id, 'code' => $inv->code]); $preview = $bot->communities->previewInvite(['code' => $inv->code]); // CommunityInvitePreview (no auth needed) $communityId = $bot->communities->acceptInvite(['code' => $inv->code]); // bot joins via code // --- Join requests (user -> community) --- $reqs = $bot->communities->getJoinRequests(['community_id' => $c->id]); // CommunityJoinRequest[] $bot->communities->approveJoinRequest(['community_id' => $c->id, 'request_id' => $reqs[0]->id]); $bot->communities->rejectJoinRequest(['community_id' => $c->id, 'request_id' => $reqs[0]->id]); // --- Group requests + linking groups --- $greqs = $bot->communities->getGroupRequests(['community_id' => $c->id]); // CommunityGroupRequest[] $bot->communities->approveGroupRequest(['community_id' => $c->id, 'request_id' => $greqs[0]->id]); $bot->communities->rejectGroupRequest(['community_id' => $c->id, 'request_id' => $greqs[0]->id]); $bot->communities->addGroup(['community_id' => $c->id, 'conversation_id' => 123]); $bot->communities->removeGroup(['community_id' => $c->id, 'conversation_id' => 123]);
Community fields: id, name, description, avatarUrl, createdBy,
announcementChannelId, requiresApproval, createdAt, role (only in list()).
CommunityDetail adds members (each with userId, name, avatarUrl, role).
webhooks
// Register $bot->webhooks->set(['url' => 'https://yourserver.com/webhook']); // β WebhookSetResult { url: string, active: bool } // Get info $info = $bot->webhooks->getInfo(); // β WebhookInfo { active: bool, url: ?string, createdAt: mixed } // Remove $bot->webhooks->delete(); // β WebhookDeleteResult { active: bool }
profile
// Bot profile $profile = $bot->profile->get(); // β BotProfile { userId, username, isBot, about, description, avatarUrl } // User profile (KappelaUser only) $profile = $user->profile->get(); // β UserProfile { id, username, nom, isBot, isPremium, avatarUrl, about }
stories (KappelaUser only)
Create and manage stories (ephemeral, 24 h) via $me->stories. Available on KappelaUser only β their audience is based on your private-conversation contacts.
For image/video stories, pass media (a ['data', 'filename', 'content_type'] array or a file path) β the SDK uploads it automatically and uses the resulting media id. For text/poll stories, no upload is needed. You can also pass a pre-uploaded media_id.
// Image story β SDK uploads the file, then creates the story $story = $me->stories->create([ 'type' => 'image', 'media' => ['data' => $bytes, 'filename' => 'photo.jpg', 'content_type' => 'image/jpeg'], // or simply: 'media' => '/path/to/photo.jpg', 'caption' => 'Sunset π', 'audience' => 'all', // 'all' (default) | 'selected' | 'excluded' ]); // Text story β no media $me->stories->create(['type' => 'text', 'caption' => 'Good morning βοΈ']); // Restricted audience $me->stories->create(['type' => 'text', 'caption' => 'PrivΓ©', 'audience' => 'selected', 'audience_user_ids' => ['uuid']]); // Clickable CTA link over the story (text or image) $me->stories->create(['type' => 'text', 'caption' => 'New drop', 'link' => 'https://shop.example.com', 'link_label' => 'Shop now']);
Link (CTA) β
link(+ optionallink_label) adds a clickable link shown over the story in the Kappela apps. The SDK carries it inside the caption as a JSON envelope ({text, link, linkLabel}); without a link the caption stays plain text.
| Method | Returns | Description |
|---|---|---|
stories->create($params) |
Story |
Create a story (uploads media automatically for image/video). |
stories->uploadMedia($file) |
StoryMediaUpload |
Upload story media manually and get a media id (usually unnecessary). |
stories->list() |
Story[] |
Feed of your contacts' active stories. |
stories->listMine() |
Story[] |
Your own stories. |
stories->get($storyId) |
Story |
A single story (audience-checked server-side). |
stories->delete($storyId) |
StoryActionResult |
Delete one of your stories. |
stories->view($storyId) |
StoryActionResult |
Mark a story as viewed. |
stories->getViewers($storyId) |
StoryView[] |
Who viewed your story (owner only). |
stories->getPreferences() |
StoryPreferences |
Your default audience preference. |
stories->setPreferences($audience, $audienceUserIds = []) |
StoryActionResult |
Set your default audience preference. |
Pause β while automations are paused, story reads still work but creating/deleting/viewing stories is rejected with
AUTOMATIONS_PAUSED.
Keyboards
Comparison
| Type | Usage | Rendered |
|---|---|---|
| Inline keyboard | Buttons attached to a message | Below the message |
| Reply keyboard | Grid of buttons (replaces input bar) | Bottom of screen |
| Scroll keyboard | Horizontal scrollable buttons | Above input bar |
Inline keyboard
Buttons are passed as a 2D array β rows Γ columns.
// Short form: text = callback_data $bot->messages->send([ 'chat_id' => $chatId, 'text' => 'Choose:', 'reply_markup' => [ 'inline_keyboard' => [[ ['text' => 'β Yes', 'callback_data' => 'yes'], ['text' => 'β No', 'callback_data' => 'no'], ]], ], ]); // Long form: separate text and callback_data $bot->messages->send([ 'chat_id' => $chatId, 'text' => 'Choose action:', 'reply_markup' => [ 'inline_keyboard' => [ [['text' => 'π¦ Orders', 'callback_data' => 'action_orders']], [['text' => 'βοΈ Settings', 'callback_data' => 'action_settings']], ], ], ]);
Reply keyboard
Grid of buttons shown at the bottom. Each item can be a plain string or an array with text + callback_data.
// Short form β text is also the callback_data $bot->messages->send([ 'chat_id' => $chatId, 'text' => 'Pick a size:', 'reply_markup' => ['keyboard' => [['S', 'M'], ['L', 'XL']]], ]); // Long form β separate text and callback_data $bot->messages->send([ 'chat_id' => $chatId, 'text' => 'Confirm?', 'reply_markup' => [ 'keyboard' => [[ ['text' => 'β Yes', 'callback_data' => 'confirm'], ['text' => 'β No', 'callback_data' => 'cancel'], ]], ], ]); // Mixed β strings and objects in the same row $bot->messages->send([ 'chat_id' => $chatId, 'text' => 'Mixed keyboard:', 'reply_markup' => [ 'keyboard' => [ [['text' => 'β Yes', 'callback_data' => 'yes'], 'No'], ], ], ]);
Scroll keyboard
Flat horizontal list. Items can be plain strings or arrays with text + callback_data.
// Short form $bot->messages->send([ 'chat_id' => $chatId, 'text' => 'Filter by:', 'reply_markup' => ['scroll_keyboard' => ['All', 'Active', 'Closed']], ]); // Long form $bot->messages->send([ 'chat_id' => $chatId, 'text' => 'Menu:', 'reply_markup' => [ 'scroll_keyboard' => [ ['text' => 'π¦ Orders', 'callback_data' => 'menu_orders'], ['text' => 'β Help', 'callback_data' => 'menu_help'], ['text' => 'βοΈ Settings', 'callback_data' => 'menu_settings'], ], ], ]); // Mixed $bot->messages->send([ 'chat_id' => $chatId, 'text' => 'Mixed scroll:', 'reply_markup' => [ 'scroll_keyboard' => [ ['text' => 'π¦ Orders', 'callback_data' => 'menu_orders'], 'β Help', ], ], ]);
Full example
<?php require 'vendor/autoload.php'; use Kappelas\KappelaBot; use Kappelas\Types\CallbackQuery; use Kappelas\Types\Message; $bot = new KappelaBot('YOUR_BOT_TOKEN'); $chatId = 123; // Show a menu with inline keyboard $bot->messages->send([ 'chat_id' => $chatId, 'text' => 'π What do you need?', 'reply_markup' => [ 'inline_keyboard' => [ [['text' => 'π¦ Orders', 'callback_data' => 'menu_orders']], [['text' => 'β Help', 'callback_data' => 'menu_help']], [['text' => 'βοΈ Settings', 'callback_data' => 'menu_settings']], ], ], ]); // Handle button clicks $bot->onCallbackQuery(function (CallbackQuery $cb) use ($bot) { $response = match($cb->callbackData) { 'menu_orders' => 'π¦ Here are your orders...', 'menu_help' => 'β How can I help you?', 'menu_settings' => 'βοΈ Opening settings...', default => 'Unknown option', }; $bot->messages->send(['chat_id' => $cb->chatId, 'text' => $response]); }); $bot->start();
Text formatting
Inline styles
*bold* β **bold**
__italic__ β _italic_
~strikethrough~ β ~~strikethrough~~
`inline code` β `code`
$bot->messages->send([ 'chat_id' => $chatId, 'text' => '*bold* __italic__ ~strikethrough~ `code`', ]);
Block code
Wrap with triple backticks. Optionally specify a language:
$bot->messages->send([ 'chat_id' => $chatId, 'text' => "Your API key:\n```\nsk_live_abc123\n```", ]);
Blockquote / citation
Lines starting with > are rendered as a blockquote:
$bot->messages->send([ 'chat_id' => $chatId, 'text' => "> Original question\n\nDetailed answer here.", ]);
Mentions and commands
$bot->messages->send([ 'chat_id' => $chatId, 'text' => 'Thanks @alice! Type /help for available commands.', ]);
Auto-detected links
Plain URLs and bare domains are automatically made clickable:
$bot->messages->send([ 'chat_id' => $chatId, 'text' => 'Visit kappelas.com or https://kappelas.com/docs', ]);
Error handling
All API errors throw KappelaError. Catch it for structured error info:
use Kappelas\KappelaError; try { $bot->messages->send(['chat_id' => $chatId, 'text' => 'Hi']); } catch (KappelaError $e) { echo $e->errorCode; // 'NOT_FOUND', 'FORBIDDEN', ... echo $e->errorMessage; // human-readable message from the API echo $e->status; // HTTP status code (int) echo $e->requestId; // trace ID (include in bug reports) }
errorCode |
HTTP | Meaning |
|---|---|---|
UNAUTHORIZED |
401 | Invalid or expired token |
FORBIDDEN |
403 | Missing permission for this action |
NOT_FOUND |
404 | Resource doesn't exist |
MISSING_FIELD |
400 | Required parameter missing |
INVALID_FIELD |
400 | Parameter has wrong type/format |
CONFLICT |
409 | Resource already exists |
INTERNAL_ERROR |
500 | Unexpected server error |
SERVICE_UNAVAILABLE |
503 | Platform temporarily unavailable |
File input
Pass file content as a raw string plus metadata:
// From bytes in memory $bot->messages->sendPhoto([ 'chat_id' => 123, 'file' => [ 'data' => file_get_contents('/path/to/photo.jpg'), 'filename' => 'photo.jpg', 'content_type' => 'image/jpeg', ], 'caption' => 'My photo', ]); // From a file path (the SDK reads it automatically) $bot->messages->sendDocument([ 'chat_id' => 123, 'file' => '/path/to/document.pdf', 'caption' => 'My document', ]);
Supported methods: sendPhoto, sendVideo, sendDocument, sendAudio.