zoplio (Python SDK)
Official Zoplio Python SDK for the Zoplio API v1. MIT licensed.
Zoplio schedules meetings for you: you say who to invite and roughly when, Zoplio negotiates with every invitee over WhatsApp/email and confirms a slot.
Requires Python >= 3.10. Depends on httpx.
Install
pip install zoplio
Roles
The account that owns the API key is the organizer. participants are the people Zoplio invites. To arrange a meeting for someone else, set organizer.
- You are a Zoplio user like anyone on WhatsApp: a meeting you create runs on your calendar, in your timezone and working hours, Zoplio tells you when the invitees answer, and it counts against your plan.
- Every participant (1-8) is invited; one is enough. Your own number or e-mail as a participant is rejected with
validation_failed. organizeris the optional on-behalf mode (an agency booking for a client): that person is then the organizer, every participant is still invited, and the meeting still bills to your account.
Usage
from zoplio import ZoplioClient, ZoplioError
zoplio = ZoplioClient(api_key="zpl_...") # base_url defaults to https://api.zoplio.com
# Create a meeting. You are the organizer; Zoplio invites Jana.
created = zoplio.schedule_meeting(
participants=[{"phone": "+420777123456", "name": "Jana"}],
title="Intro call",
duration_minutes=30,
preferred_date="2026-09-16",
preferred_time="14:00",
timezone="Europe/Prague", # always send it with preferred_time
idempotency_key="order-42-intro-call", # optional, safe retries
)
print(created["meetingId"], created["status"], created["proposedSlots"])
# Poll status (or use webhooks instead). Each entry of meeting["participants"]
# carries status, attending and role ("organizer" | "participant").
meeting = zoplio.get_meeting(created["meetingId"])
# List / reschedule / cancel.
zoplio.list_meetings(status="confirmed", limit=10)
zoplio.reschedule_meeting(
created["meetingId"],
preferred_date="2026-09-17",
preferred_time="10:00",
timezone="Europe/Prague",
idempotency_key="order-42-intro-call-move-1", # a retry replays instead of opening another round
)
zoplio.cancel_meeting(created["meetingId"])
On behalf of someone else: set organizer. Petr is then the organizer (his calendar and timezone; Zoplio tells him the meeting is being arranged and again once it confirms) and Jana is invited:
zoplio.schedule_meeting(
organizer={"email": "petr@example.com", "name": "Petr"},
participants=[{"phone": "+420777123456", "name": "Jana"}],
title="Intro call",
preferred_date="2026-09-16",
preferred_time="14:00",
timezone="Europe/Prague",
)
Scheduling mode is picked by the date fields you send: exact (preferred_date + preferred_time + timezone), day (preferred_date only), range (earliest_date + latest_date) or open ask (open_ask=True plus the window). With no date fields Zoplio proposes one slot per working day over the next seven days at the start of the organizer's working hours (09:00 by default), rendered in each invitee's own timezone.
Errors
Every non-2xx response raises ZoplioError with the contract envelope:
try:
zoplio.get_meeting("nope")
except ZoplioError as err:
err.status_code # 404
err.code # 'not_found' | 'unauthorized' | 'rate_limited' | 'validation_failed' | 'conflict' | 'quota_exceeded' | 'upstream_error'
str(err) # human-readable message
err.details # [{"field", "message"}] on validation_failed
quota_exceeded (HTTP 402) means your account's free-plan limit was reached this calendar month, the same limits as for any Zoplio user: 3 confirmed meetings and 15 meeting requests per calendar month (UTC). Meetings still being arranged count against the 3 until they confirm or fall through; str(err) says which limit it was.
Webhooks
# Subscribe. The secret is returned exactly once.
hook = zoplio.create_webhook(
url="https://example.com/zoplio-hook",
events=["meeting.confirmed", "meeting.rescheduled", "meeting.cancelled"],
# omit `events` for all five: meeting.created, meeting.confirmed,
# meeting.cancelled, meeting.rescheduled, negotiation.failed
)
save_secret(hook["secret"]) # whsec_...
zoplio.list_webhooks()
zoplio.delete_webhook(hook["id"])
meeting.rescheduled fires the moment a confirmed meeting re-opens to move; its payload carries previousSlot, and a fresh meeting.confirmed (or a cancellation) follows when the renegotiation resolves.
Verify deliveries with the static helper. Pass the RAW request body:
# e.g. Flask
@app.post("/zoplio-hook")
def zoplio_hook():
ok = ZoplioClient.verify_webhook_signature(
request.get_data(), # raw bytes
request.headers.get("X-Zoplio-Signature", ""),
os.environ["ZOPLIO_WEBHOOK_SECRET"], # whsec_...
)
if not ok:
return "", 401
delivery = request.get_json() # {"event", "payload", "timestamp"}
return "", 200
Development
pip install -e ".[dev]"
python -m pytest
python -m mypy src/zoplio
Metadata
Release files for zoplio 0.3.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 | |
|---|---|---|---|
| zoplio-0.3.0.tar.gz | 12.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zoplio-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.0 kB
Release files / zoplio-0.3.0.tar.gz
| Download URL | zoplio-0.3.0.tar.gz |
|---|---|
| Size | 12.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e70df66c154e8d365d41b66381f676e7c2511c87d077de796f5fde0b05d927fe
|
|
BLAKE2b-256 checksum How to use checksums |
3f3eadd172efbac6a55a9ba1218fbec0a061a5db44fb6c7231ea47b7d923bec3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / zoplio-0.3.0-py3-none-any.whl
| Download URL | zoplio-0.3.0-py3-none-any.whl |
|---|---|
| Size | 10.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
67006ccdb8a264e66ff19ceb3693078d9d6d7067543e6fdac43943fbbc819865
|
|
BLAKE2b-256 checksum How to use checksums |
5aeea4bb3d7ed591e210c6cc9cdc64fe6a47923d4a9281fccd8f96836520ca19
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|