tokolaku
Official Python SDK for the Tokolaku Engine API — AI bot replies, omnichannel messaging (WhatsApp/Instagram/Messenger), and webhook verification.
Install
pip install tokolaku
Requirements
- Python >= 3.10
Quickstart
Bahasa Indonesia ringkas: buat instance Tokolaku dengan API key, lalu panggil bot_reply untuk balasan AI atau messages.send untuk kirim pesan lewat channel resmi (WhatsApp/Instagram/Messenger) yang sudah terhubung.
import os
from tokolaku import Tokolaku
tokolaku = Tokolaku(os.environ["TOKOLAKU_API_KEY"])
# or with options: Tokolaku(api_key=..., base_url=..., timeout=30.0, max_retries=2)
# 1. AI bot reply for a single customer message
reply = tokolaku.bot_reply(
"Halo, apakah produk ini ready stock?",
session_id="wa:628123456789", # keeps multi-turn context
)
print(reply.reply, reply.parts)
# 2. Send a text message through a connected channel
sent = tokolaku.messages.send(
to="628123456789",
text="Terima kasih sudah menghubungi kami!",
channel_id="ch_abc123",
)
print(sent.id, sent.status)
messages.send also accepts a business-initiated template message — pass template instead of text (exactly one of the two, never both):
tokolaku.messages.send(
to="628123456789",
template={"name": "order_update", "language": "id", "category": "utility"},
channel_id="ch_abc123",
)
Error handling
Every failed request raises an instance of TokolakuAPIError (or one of its subclasses). .status is None when the request never got an HTTP response (network error, timeout); .code is the backend's machine-readable error code when available.
| Class | HTTP status | When it's raised |
|---|---|---|
TokolakuValidationError |
400, 422 | Invalid request params — also raised client-side before any network call (e.g. messages.send with both text and template, or neither) |
TokolakuAuthenticationError |
401 | Missing or invalid API key |
TokolakuInsufficientBalanceError |
402 | Tenant balance too low to cover the charge |
TokolakuPermissionError |
403 | API key lacks permission for this action |
TokolakuRateLimitError |
429 | Rate limit exceeded |
TokolakuAPIError |
any other status, or None |
Base class — also covers network errors, timeouts, and malformed responses not mapped above |
TokolakuWebhookSignatureError |
— | Webhook signature missing or invalid (does not extend TokolakuAPIError) |
import os
from tokolaku import (
Tokolaku,
TokolakuAPIError,
TokolakuInsufficientBalanceError,
TokolakuRateLimitError,
)
tokolaku = Tokolaku(os.environ["TOKOLAKU_API_KEY"])
try:
tokolaku.bot_reply("Halo")
except TokolakuInsufficientBalanceError:
... # top up balance, notify the tenant
except TokolakuRateLimitError:
... # back off and retry later
except TokolakuAPIError as err:
print(err.status, err.code, err)
Retry policy
The SDK retries automatically (max_retries, default 2) using exponential backoff with full jitter (base 250ms, capped at 1s; a Retry-After response header wins when present). The policy is money-aware: it only retries when a retry cannot cause a duplicate side effect.
| Condition | bot_reply |
messages.send |
|---|---|---|
429 Too Many Requests |
Retried | Retried |
| Network error (request never got a response) | Retried | Retried |
5xx server error |
Retried | Not retried |
Timeout (code: "timeout") |
Not retried | Not retried |
2xx with malformed JSON body (code: "invalid_response") |
Not retried | Not retried |
2xx where the body stream fails mid-read (code: "response_read_error") |
Not retried | Not retried |
bot_replyhas no side effect if it fails, so it retries on429, any5xx, and network errors.messages.sendis NOT retried on timeout/5xx because the message may already have been sent and charged even though the client never saw a successful response, and the API does not yet expose an idempotency key. It only retries on429and network errors — a network retry only applies when the request itself failed before any response headers arrived (no response headers were ever received, so the send most likely never reached the server). Once response headers have arrived, a failure reading the body is aresponse_read_error, not a network error, and is never retried.- A timeout (
code: "timeout") is never retried on either endpoint, since it's ambiguous whether the server received/processed the request. - A
2xxresponse with a body that fails to parse as JSON (code: "invalid_response") carries the actual 2xx status the server returned (usually200) and is never retried on either endpoint — the request already reached the server and had its side effect (reply generated / message sent and charged); retrying would risk a double-send or burning AI quota for nothing. - A
2xxresponse whose body stream errors mid-read (code: "response_read_error", e.g. the connection resets after headers arrive) is likewise never retried, for the same reason: response headers arriving means the request already reached the server and may have had its side effect, even though the body was never fully read.
Webhooks
Verify the x-tokolaku-signature header (sha256=<hex>, HMAC-SHA256 of the raw request body) before trusting a webhook payload. Always use the raw, unmodified request body — a re-serialized JSON string will not match the signature.
from tokolaku.webhooks import verify_webhook_signature, construct_event, TokolakuWebhookSignatureError
Flask
import os
from flask import Flask, request, jsonify
from tokolaku.webhooks import construct_event, TokolakuWebhookSignatureError
app = Flask(__name__)
@app.post("/webhooks/tokolaku")
def tokolaku_webhook():
raw_body = request.get_data() # raw bytes — do NOT use request.get_json() here
try:
result = construct_event(
raw_body,
request.headers.get("x-tokolaku-signature"),
os.environ["TOKOLAKU_WEBHOOK_SECRET"],
)
except TokolakuWebhookSignatureError:
return jsonify({"error": "invalid signature"}), 401
event = result["event"]
# ... handle event
return jsonify({"received": True})
FastAPI
import os
from fastapi import FastAPI, Request, HTTPException
from tokolaku.webhooks import construct_event, TokolakuWebhookSignatureError
app = FastAPI()
@app.post("/webhooks/tokolaku")
async def tokolaku_webhook(request: Request):
raw_body = await request.body() # raw bytes — do NOT depend on request.json() here
try:
result = construct_event(
raw_body,
request.headers.get("x-tokolaku-signature"),
os.environ["TOKOLAKU_WEBHOOK_SECRET"],
)
except TokolakuWebhookSignatureError:
raise HTTPException(status_code=401, detail="invalid signature")
event = result["event"]
# ... handle event
return {"received": True}
License
MIT
Docs
Full API reference: https://tokolaku.id/api-docs
Metadata
Release files for tokolaku 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| tokolaku-1.1.0.tar.gz | 14.3 kB | Without provenance | Details for tokolaku-1.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| tokolaku-1.1.0-py3-none-any.whl | Python 3 | none | any | Details for tokolaku-1.1.0-py3-none-any.whl |
Total release size: 25.8 kB
Release files / tokolaku-1.1.0.tar.gz
| Download URL | tokolaku-1.1.0.tar.gz |
|---|---|
| Size | 14.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
70c922edbe2add832092cbaef93a0de5dd223d90e95339c74a17cd24c7b2a32b
|
|
BLAKE2b-256 checksum How to use checksums |
d059d945a175ccd20333f45860f4a6d82f8650c94e63fd966e6a70ab15685cfa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / tokolaku-1.1.0-py3-none-any.whl
| Download URL | tokolaku-1.1.0-py3-none-any.whl |
|---|---|
| Size | 11.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cb1a296fba581d0db4da6966420c2c8379ce185b8b143b9e6a5e527416f6df47
|
|
BLAKE2b-256 checksum How to use checksums |
0b58421cf4768ac6c14bbb4818789809baba034a4184df4bef1b20048f96fc58
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for tokolaku 1.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.