Official Python client for the WaAPI REST API — send and receive WhatsApp messages, manage chats, groups and channels from Python.
pip install waapifrom waapi import WaAPI
client = WaAPI(token="YOUR_API_TOKEN", instance_id=123)
client.send_message(
chat_id="4915112345678@c.us",
message="Deployment finished.",
)Get a token at waapi.app/user/api-tokens and create an instance connected to your number.
Its suffix decides where the message lands, and a wrong suffix is accepted and delivers nothing:
| Target | Format |
|---|---|
| One person | 4915112345678@c.us |
| Group | 123456789-123456789@g.us |
| Channel | 123456789@newsletter |
Same method names, awaited:
from waapi import AsyncWaAPI
async with AsyncWaAPI(token="YOUR_API_TOKEN", instance_id=123) as client:
await client.send_message(chat_id="4915112345678@c.us", message="Hi")A successful HTTP exchange is not proof the message was sent. The API answers
200 with {"status": "error"} when, for example, the instance is not
connected — so the SDK raises on that too, rather than handing back a body that
looks like success.
from waapi import WaAPI, FailedActionError, AuthenticationError, RateLimitError
try:
client.send_message(chat_id="4915112345678@c.us", message="Hi")
except AuthenticationError:
... # token wrong, expired, or missing scopes
except RateLimitError as e:
time.sleep(e.retry_after or 5)
except FailedActionError as e:
... # accepted but not carried out — e.response has the detail| Exception | Raised on |
|---|---|
AuthenticationError |
HTTP 401, 403 |
NotFoundError |
HTTP 404 |
ValidationError |
HTTP 422 — .errors holds the field errors |
RateLimitError |
HTTP 429 — .retry_after in seconds when the API sends it |
FailedActionError |
HTTP 400, and HTTP 200 with status: error |
ServerError |
HTTP 5xx |
All inherit from WaAPIError.
All 122 client actions are wrapped, typed, and available on both clients:
client.create_group(group_name="Ops", group_participants=["4915112345678@c.us"])
client.send_media(chat_id="4915112345678@c.us", media_url="https://example.com/report.pdf")
client.get_contacts()They are generated from the same OpenAPI specification the n8n node and the MCP tools come from, so they track the API instead of drifting behind it — see CONTRIBUTING.md.
An action added to the API since the last release is still reachable by name:
client.action("some-new-action", {"chatId": "4915112345678@c.us"})WaAPI(
token="...", # required
instance_id=123, # optional; per-call instance_id overrides it
base_url="https://waapi.app/api/v1",
timeout=30.0,
)Passing instance_id to the client keeps single-instance code short. Any call
can still override it, and a call with neither raises before a request is sent.
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytestThe suite runs entirely against httpx.MockTransport — no network, no token,
no connected account.
MIT. Not affiliated with, endorsed or sponsored by WhatsApp LLC or Meta. WhatsApp is a trademark of WhatsApp LLC.