Skip to content

API Reference

Overview

The BotBell API lets your programs and scripts send push notifications to your Apple devices and receive replies. No SDK required — just simple HTTP requests.

Base URL: https://api.botbell.app

How It Works

  1. Create a bot in the BotBell app and copy its push URL
  2. POST a JSON message to the push URL from any language or tool
  3. Receive an instant push notification on all your devices
  4. Optionally, receive your replies via Reply URL callback or polling API

Response Format

All API responses use the same JSON envelope. A code of 0 means success; any other code indicates an error.

// Success
{
  "code": 0,
  "message": "success",
  "data": { ... }
}

// Error
{
  "code": 40001,
  "message": "Invalid bot token"
}

Push a Message

Send a notification from your bot to the BotBell app. Your bot's push URL already contains the authentication token — just POST to it.

Endpoint

POST https://api.botbell.app/v1/push/<your_bot_token>

No Authorization header needed. The token in the URL is your authentication.

Alternatively, you can use POST /v1/messages/push with the X-Bot-Token: <token> header.

Request Body (JSON)

FieldTypeRequiredDescription
messagestringYesThe message text (max 4,096 characters)
titlestringNoTitle displayed above the message (max 256 characters)
urlstringNoA tappable link attached to the notification
image_urlstringNoURL of an image to show in the notification
summarystringNoCustom summary for long messages. If omitted, auto-truncated from message (max 512 chars)
formatstringNoMessage format: 'text' (default) or 'markdown' for Markdown rendering
actions_descriptionstringNoDescription text shown above action buttons (max 256 chars)
actionsarrayNoQuick reply buttons (max 5). See Actions below.

Action Fields

FieldTypeRequiredDescription
keystringYesAction identifier returned when user taps (max 64 chars)
labelstringYesButton text displayed to user (max 64 chars)
typestringNo"button" (default) sends label as reply; "input" opens a text field for custom input
placeholderstringNoPlaceholder text for input field (max 128 chars, only for type "input")
Actions Example
{
  "message": "Deploy v2.3.0 to production?",
  "title": "Deploy Request",
  "actions_description": "How would you like to proceed?",
  "actions": [
    {"key": "approve", "label": "Approve"},
    {"key": "reject", "label": "Reject"},
    {"key": "custom", "label": "Other...", "type": "input", "placeholder": "Enter your reason"}
  ]
}

Example Request

curl
curl -X POST https://api.botbell.app/v1/push/bt_your_token \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Server CPU is at 95%!",
    "title": "Alert",
    "url": "https://grafana.example.com/dashboard"
  }'

Response

{
  "code": 0,
  "message": "success",
  "data": {
    "message_id": "msg_abc123",
    "delivered": true,
    "timestamp": 1709827200
  }
}
FieldDescription
message_idUnique ID for this message
deliveredtrue if the push notification was sent to APNs successfully; false if no device is registered
timestampUnix timestamp (seconds) when the message was created

Try It Now

Paste your bot token and send a real push notification to your device.


Receiving Replies

When you reply to a bot in the app, BotBell can forward your reply to your server automatically. This is optional — you only need this if your bot program handles two-way conversations.

Setting Up a Reply URL

In the BotBell app, go to your bot's settings and enter a Reply URL (e.g. https://your-server.com/botbell/reply). BotBell will POST your replies to this URL as they happen.

Callback Payload

When you reply, BotBell sends a POST request to your Reply URL with the following format:

POST https://your-server.com/botbell/reply
Headers:
  Content-Type: application/json
  X-Webhook-Signature: sha256=abc123...
  X-Webhook-Timestamp: 1709827300

Body:
{
  "event": "user_reply",
  "bot_id": "bot_abc123",
  "message_id": "msg_xyz789",
  "content": "Got it, thanks!",
  "timestamp": 1709827300,
  "action": "approve"
}

Signature Verification

Each callback includes an HMAC-SHA256 signature so you can verify it came from BotBell. The signing key is the Webhook Secret shown in your bot settings.

signature = HMAC-SHA256(
  key:     <your_webhook_secret>,
  message: "<timestamp>.<raw_request_body>"
)

To verify the signature:

  1. Extract the X-Webhook-Timestamp and X-Webhook-Signature headers
  2. Check that the timestamp is within 5 minutes of the current time (to prevent replay attacks)
  3. Compute HMAC-SHA256 over the string "<timestamp>.<raw_body>" using your Webhook Secret as the key
  4. Compare the computed value with the signature after the "sha256=" prefix

Your Server's Response

Return any HTTP 2xx status code to acknowledge receipt. Non-2xx responses or timeouts (5 seconds) are treated as failures — the reply will be stored in the polling queue for 24 hours so you can retrieve it later.


Polling for Replies

If you don't have a public server to receive callbacks, you can poll for replies instead. This is also useful as a fallback when your Reply URL is temporarily down.

Endpoint

GET https://api.botbell.app/v1/messages/poll
Headers:
  X-Bot-Token: <your_bot_token>

Query Parameters

FieldTypeDefaultDescription
sinceinteger-Only return messages after this Unix timestamp
limitinteger20Number of messages to return (max 100)

Response

{
  "code": 0,
  "message": "success",
  "data": {
    "messages": [
      {
        "message_id": "msg_xyz789",
        "content": "Got it, thanks!",
        "timestamp": 1709827300,
        "action": "approve"
      }
    ],
    "has_more": false
  }
}

Note: Polled messages are marked as read and will not be returned again. Unpolled messages expire after 24 hours.


Error Codes

CodeHTTP StatusDescription
40001401Invalid or missing bot token
40010400Request validation failed (e.g. missing message field, body too large)
40029429Rate limit exceeded — too many requests per minute
40030403Monthly message quota exhausted
40031403Bot limit reached for your plan
50000500Internal server error

Rate Limits & Quotas

LimitValue
Push rate (per bot)30
Message body (characters)4,096
Title (characters)256
Free monthly quota300 messages / account
Poll message retention24h
Reply URL timeout5s

Code Examples

Sending Messages

curl
curl -X POST https://api.botbell.app/v1/push/bt_your_token \
  -H "Content-Type: application/json" \
  -d '{"message":"Server CPU at 95%","title":"Alert"}'
Python
import requests

resp = requests.post(
    "https://api.botbell.app/v1/push/bt_your_token",
    json={
        "message": "Build #42 succeeded",
        "title": "CI/CD",
        "url": "https://ci.example.com/builds/42",
    },
)
print(resp.json())  # {"code": 0, "data": {"message_id": "...", ...}}
JavaScript / Node.js
const resp = await fetch("https://api.botbell.app/v1/push/bt_your_token", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    message: "New order received!",
    title: "E-Commerce",
  }),
});
const data = await resp.json();
console.log(data);
Go
package main

import (
    "bytes"
    "encoding/json"
    "net/http"
)

func main() {
    body, _ := json.Marshal(map[string]string{
        "message": "Disk usage above 90%",
        "title":   "Alert",
    })
    http.Post(
        "https://api.botbell.app/v1/push/bt_your_token",
        "application/json",
        bytes.NewReader(body),
    )
}
PHP
<?php
$ch = curl_init('https://api.botbell.app/v1/push/bt_your_token');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'message' => 'Payment received: $49.99',
        'title' => 'Stripe',
    ]),
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);

Verifying Reply Signatures

Python
import hmac, hashlib

def verify_signature(payload: bytes, timestamp: str, signature: str, secret: str) -> bool:
    message = f"{timestamp}.{payload.decode()}"
    expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)
Node.js
const crypto = require("crypto");

function verifySignature(payload, timestamp, signature, secret) {
  const message = `${timestamp}.${payload}`;
  const expected = crypto.createHmac("sha256", secret).update(message).digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expected}`),
    Buffer.from(signature),
  );
}