# EzLife · Bot API documentation

https://im-api.ez7.life/en/

Updated: 2026-11-10

A complete guide to BotMother, messaging, verification-code copying and reply keyboards. Examples use placeholders.

## Overview and capabilities

The public methods are `getMe`, `sendMessage` and `getUpdates`. Send private/group text messages and receive events that may include non-text content.

Use `copy_text` to show a code-copy icon, and `reply_markup` to display text buttons below the private-chat composer.

This domain hosts documentation. The API remains at im.ez7.life under /bot-api/. Recipients can copy their ID in Settings → Account details → ID. Reply using an incoming message.chat.id; for group events it is the group ID.

```text
https://im.ez7.life/bot-api/<BOT_TOKEN>/<METHOD>
```

## Quick start

1. Find and add `@BotMother`, then open bot management at the top of the conversation.
2. Create a bot, copy its Token and store it in the backend environment variable `EZLIFE_BOT_TOKEN`.
3. Call `getMe` to verify identity, then call `sendMessage` with a recipient user ID.
4. Start polling `getUpdates`; persist the next offset only after processing succeeds.

```bash
python3 bot.py
```

## BotMother management

Use a 1–64 character display name. In the creation form, enter a 3–28 character username prefix beginning with a letter, using letters, digits and underscores. The form adds `_bot`; check availability before creating.

Management lets you search your bots, view Bot ID, status, creation time and a `****` plus six-character suffix, and reissue or revoke Tokens. The bot quota is configured by the administrator.

| Command | Purpose |
| --- | --- |
| `/start` | Show management instructions |
| `/newbot` | Start bot creation |
| `/mybots` | List your bots |
| `/token @username` | Reissue a Token; invalidate the old one |
| `/revoke @username` | Revoke the current Token |
| `/cancel` | Cancel the creation flow |
| `/help` | Show help |

## Tokens and security

The full Token appears only when created or reissued. It is the bot password; keep it in backend configuration, outside websites, app packages, public repositories and logs.

Reissuing immediately invalidates the previous Token; revoking blocks external calls. A masked suffix cannot recover a full Token. Reissue and update your program if it is lost.

Legacy suffixes are recorded on the next valid Bot API call. General account API Tokens from account details serve account endpoints; this Bot API uses the bot Token issued by BotMother.

## getMe · Bot identity

`POST https://im.ez7.life/bot-api/<BOT_TOKEN>/getMe`

No business parameters are required. Returns the Bot ID, display name, username and group-capability flags. Start here to verify the Token in your environment.

```bash
curl --fail-with-body --request POST \
  "https://im.ez7.life/bot-api/${EZLIFE_BOT_TOKEN}/getMe" \
  --header 'Content-Type: application/json' --data '{}'
```

```json
{
  "ok": true,
  "result": {
    "id": "1000000000",
    "is_bot": true,
    "display_name": "Example Bot",
    "username": "example_bot",
    "can_join_groups": true,
    "can_read_all_group_messages": true
  }
}
```

## sendMessage · Send text

`POST https://im.ez7.life/bot-api/<BOT_TOKEN>/sendMessage`

Use `POST application/json`. Always send `chat_id` as a string: a recipient user ID for private chats, or a group ID with `chat_type: "group"`.

Text must contain 1–5000 characters; surrounding whitespace is trimmed. Record the returned message_id for your business. Sending has no idempotency key; after a timeout, check the outcome before resending.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `chat_id` | string | Yes | Recipient user ID, or group ID for group chats. Always a string. |
| `text` | string | Yes | Message text, 1–5000 characters. |
| `chat_type` | string | No | Defaults to private; use group for groups. supergroup is also accepted. |
| `copy_text` | string | No | A complete 4–12 digit string occurring in text. |
| `reply_markup` | object / JSON string | No | Optional reply keyboard object, or a JSON-encoded string. |

```bash
curl --fail-with-body --request POST \
  "https://im.ez7.life/bot-api/${EZLIFE_BOT_TOKEN}/sendMessage" \
  --header 'Content-Type: application/json' --data-binary @- <<'JSON'
{
  "chat_id": "USER_ID",
  "text": "Your order has been processed."
}
JSON
```

```json
{
  "chat_id": "GROUP_ID",
  "chat_type": "group",
  "text": "Your order has been processed."
}
```

```json
{
  "ok": true,
  "result": {
    "message_id": "EXAMPLE_MESSAGE_ID",
    "date": 1794297600,
    "chat": {
      "id": "USER_ID",
      "type": "private"
    },
    "text": "Your order has been processed."
  }
}
```

Successful sends return ok: true and result, with copy_text for codes, and reply_markup plus reply_keyboard_revision for menus. Receiving returns a result array; no updates means result: [].

## Copy verification codes

Keep the system name, purpose and expiry in the message, then add copy_text. It must be a complete 4–12 digit ASCII string appearing in text. Strings preserve leading zeros.

The icon copies only the code. Your application generates codes and enforces expiry and verification; the API field only controls display and copying. Older clients can still read the message.

```json
{
  "chat_id": "USER_ID",
  "text": "Example sign-in code: 003812. Valid for 5 minutes.",
  "copy_text": "003812"
}
```

## Reply keyboards

This is a multirow text reply keyboard. Clicking sends the button label as a normal user message; handle it through message.text in getUpdates.

Use 1–8 rows, 1–4 buttons per row, at most 24 buttons. Labels contain 1–64 characters without blank values, line breaks or controls. A button is a string or an object containing only text.

Omitting reply_markup keeps the menu; a new keyboard replaces it. Use remove_keyboard: true alone to remove the menu and its show control.

Users can hide or show the menu. One-time keyboards hide only after a successful button send; a new menu version opens again. State is isolated per user and bot, checked about every five seconds in active bot chats.

Keyboards are private-chat only; group messages with reply_markup return 422. Message text is still required. Use JSON true/false, not strings. Unknown markup/button properties are rejected. JSON-string reply_markup is limited to 16 KiB.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `reply_markup` | object / JSON string | No | Optional reply keyboard object, or a JSON-encoded string. |
| `keyboard` | array[] | Yes | Two-dimensional array: one inner array per row. |
| `resize_keyboard` | boolean | No | Default true for compact buttons; false uses taller buttons. |
| `one_time_keyboard` | boolean | No | Default false; true hides after a successful button send. |
| `remove_keyboard` | boolean | No | True clears the menu; cannot combine with other keyboard options. |

```json
{
  "chat_id": "USER_ID",
  "text": "Choose a service:",
  "reply_markup": {
    "keyboard": [
      [
        {
          "text": "Language settings"
        },
        {
          "text": "Track parcel"
        }
      ],
      [
        {
          "text": "Notifications"
        },
        {
          "text": "Enter tracking number"
        }
      ]
    ],
    "resize_keyboard": true,
    "one_time_keyboard": false
  }
}
```

```json
{
  "chat_id": "USER_ID",
  "text": "Choose a service:",
  "reply_markup": {
    "keyboard": [
      [
        "Language settings",
        "Track parcel"
      ]
    ],
    "one_time_keyboard": true
  }
}
```

```json
{
  "chat_id": "USER_ID",
  "text": "This operation has finished.",
  "reply_markup": {
    "remove_keyboard": true
  }
}
```

## getUpdates · Receive messages

`POST https://im.ez7.life/bot-api/<BOT_TOKEN>/getUpdates`

This is short polling: no messages returns an empty array immediately. Poll every 1–3 seconds and back off after failures. Public webhooks and long polling are not available.

After successful processing, set the next offset to the last update_id + 1. Earlier updates are acknowledged. On failure, keep the old offset and use update_id or a business key for deduplication.

Coordinate consumers of the same bot so separate processes do not advance each other’s offset. Inspect content_type and raw_content for non-text events.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `offset` | integer | No | Nonnegative next update ID; defaults to 0. |
| `limit` | integer | No | 1–100 updates per request; defaults to 100. |

```bash
curl --fail-with-body --request POST \
  "https://im.ez7.life/bot-api/${EZLIFE_BOT_TOKEN}/getUpdates" \
  --header 'Content-Type: application/json' \
  --data '{"offset": 0, "limit": 100}'
```

```json
{
  "ok": true,
  "result": [
    {
      "update_id": 120,
      "message": {
        "message_id": "EXAMPLE_MESSAGE_ID",
        "date": 1794297600,
        "chat": {
          "id": "1000000001",
          "type": "private"
        },
        "from": {
          "id": "1000000001",
          "display_name": "Example User"
        },
        "text": "Language settings",
        "content_type": 101,
        "raw_content": "{\n  \"content\": \"Language settings\"\n}"
      }
    }
  ]
}
```

| Parameter | Description |
| --- | --- |
| `update_id` | Increasing update ID for consumption acknowledgements. |
| `message_id` | Platform message ID. |
| `date` | Unix timestamp in seconds. |
| `chat` | Sender user ID for private chats, or group ID, with private/group type. |
| `from` | The actual sender’s ID and display name. |
| `text` | Text content; may be empty for non-text events. |
| `content_type` | Content type; 101 is text. |
| `raw_content` | Original content for received image, file and other events. |

## Complete Python example

The complete standard-library Python example handles /start, /ping and button labels, and persists its offset. Run it in a private working directory.

Replace the sample button handlers with business logic. Make orders, payments and similar actions idempotent before storing consumption progress.

```python
import json
import os
import time
import urllib.error
import urllib.request
from pathlib import Path

TOKEN = os.environ["EZLIFE_BOT_TOKEN"]
BASE = "https://im.ez7.life/bot-api/" + TOKEN
OFFSET_FILE = Path("bot-offset.json")
MENU = [
  [
    "Language settings",
    "Track parcel"
  ],
  [
    "Notifications",
    "Enter tracking number"
  ]
]

class ApiFailure(Exception):
    pass

class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, *args):
        return None

HTTP = urllib.request.build_opener(NoRedirect())

def api(method, payload):
    req = urllib.request.Request(
        BASE + "/" + method,
        data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    try:
        with HTTP.open(req, timeout=20) as response:
            body = json.load(response)
    except urllib.error.HTTPError as error:
        raise ApiFailure("HTTP " + str(error.code)) from None
    except (urllib.error.URLError, TimeoutError, OSError, ValueError):
        raise ApiFailure("Network or response failure") from None
    if not body.get("ok"):
        raise ApiFailure("API error " + str(body.get("error_code", "unknown")))
    return body["result"]

def handle(update):
    message = update.get("message", {})
    chat = message.get("chat", {})
    text = message.get("text", "")
    if not chat.get("id"):
        return
    payload = {"chat_id": str(chat["id"]),
               "chat_type": chat.get("type", "private")}
    if text == "/start" and payload["chat_type"] == "private":
        payload.update(text="Choose a service:", reply_markup={
            "keyboard": MENU, "resize_keyboard": True,
            "one_time_keyboard": False,
        })
    elif text == "/ping":
        payload["text"] = "pong"
    elif text in [label for row in MENU for label in row]:
        payload["text"] = "This operation has finished."
    else:
        return
    api("sendMessage", payload)

def save_offset(value):
    temp = OFFSET_FILE.with_suffix(".tmp")
    fd = os.open(temp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
    with os.fdopen(fd, "w", encoding="utf-8") as stream:
        json.dump(value, stream)
        stream.flush()
        os.fsync(stream.fileno())
    os.replace(temp, OFFSET_FILE)

def main():
    offset = json.loads(OFFSET_FILE.read_text()) if OFFSET_FILE.exists() else 0
    if isinstance(offset, bool) or not isinstance(offset, int) or offset < 0:
        raise ValueError("Invalid offset file")
    delay = 1
    while True:
        try:
            updates = api("getUpdates", {"offset": offset, "limit": 100})
            for update in updates:
                handle(update)
                next_offset = int(update["update_id"]) + 1
                save_offset(next_offset)
                offset = next_offset
            delay = 1
            time.sleep(2)
        except (ApiFailure, OSError, ValueError):
            print("Processing failed; offset retained. Check configuration.", flush=True)
            time.sleep(delay)
            delay = min(delay * 2, 30)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        pass
```

## Groups and message scope

The bot must join the target group first. Owner approval is enabled by default. Review pending invitations in the bot conversation under More → Bot settings → Joined groups.

Group privacy mode is off by default: receiving-enabled groups do not need @mentions. A bot’s own messages are excluded from its own queue; group lifecycle notices are not ordinary messages.

The per-group receiving switch controls updates, forwarding and auto replies for that group. User-owned bots forward enabled group messages to their owner’s private chat with a group-name prefix. External access still controls API calls and update delivery.

## Bot settings and auto replies

Only the owner manages join approval, external access, per-group receiving and keyword auto replies. Disabling external access retains the Token but rejects program calls.

Up to 50 rules: keywords 1–100 characters, replies 1–2000. Rules match case-insensitive containment in order; the first enabled match replies.

Auto replies work in private chats and receiving-enabled groups, with protection against bot reply chains. AI API integration is currently planned and has no public method.

## Error handling

Early input errors, such as invalid JSON or an oversized body, use errorCode and message. Other API failures use error_code and description. Always check HTTP status and ok first.

```json
{
  "ok": false,
  "error_code": 422,
  "description": "chat_id 与 1–5000 字的 text 为必填"
}
```

| Status | Action |
| --- | --- |
| 400 | Invalid JSON. Check encoding and syntax. |
| 401 | Token invalid, revoked, reissued, or bot suspended. Update credentials. |
| 403 | The owner disabled external access. Enable it in bot settings. |
| 404 | Check the /bot-api/ path, Token format and method name. |
| 409 | A menu update for this bot/user is in progress. Retry shortly. |
| 413 | The request body exceeds 1 MiB. Reduce its size. |
| 422 | Correct parameters, code formatting or keyboard structure. |
| 500 | Temporary failure. Keep receive offset and back off; check send outcomes before retrying. |

## Limits and client support

This guide covers the current public API. Media sending, inline callback buttons, contact/location request buttons and public webhooks have no public methods yet.

Reply keyboards require a current Web/H5 client. Clients bundling older H5 resources need an update to display new controls.

The complete request body is limited to 1 MiB. Adjust send and polling rates to actual load; this guide promises no fixed throughput. Group capability flags do not replace group membership, approval or receiving permissions.

