A small, friendly Python wrapper around the Vapi voice-AI API. It ships both a synchronous and an asynchronous client, covers the full REST API through a uniform CRUD interface, and returns plain parsed JSON so you're never fighting the library.
from vapi_agent import VapiAgent
client = VapiAgent(api_key="your-private-key")
for assistant in client.assistants.list(limit=20):
print(assistant["id"], assistant["name"])pip install vapi-agentOr, from a local checkout:
pip install -e .Requires Python 3.8+ and depends only on httpx.
Get your private API key from the Vapi dashboard.
Pass it directly or set the VAPI_API_KEY environment variable:
client = VapiAgent(api_key="your-private-key")
# ...or, with VAPI_API_KEY set in the environment:
client = VapiAgent()Use the private key server-side only. Never ship it in client-side code.
Every resource exposes the same five methods —
list(), get(id), create(**fields), update(id, **fields), delete(id):
| Attribute | Vapi endpoint |
|---|---|
client.assistants |
/assistant |
client.calls |
/call |
client.phone_numbers |
/phone-number |
client.tools |
/tool |
client.squads |
/squad |
client.workflows |
/workflow |
client.files |
/file |
client.knowledge_bases |
/knowledge-base |
client.test_suites |
/test-suite |
client.analytics |
/analytics (.query(...)) |
client.logs |
/logs (read-only, .list(...)) |
Request bodies are passed as keyword arguments and forwarded as JSON, so you can use anything the Vapi API accepts without waiting for the library to add a field.
assistant = client.assistants.create(
name="Support Bot",
model={
"provider": "openai",
"model": "gpt-4o",
"messages": [{"role": "system", "content": "You are a helpful agent."}],
},
voice={"provider": "11labs", "voiceId": "burt"},
firstMessage="Hi! How can I help you today?",
)
print(assistant["id"])call = client.calls.create(
assistantId=assistant["id"],
phoneNumberId="your-phone-number-id",
customer={"number": "+15551234567"},
)Vapi's list endpoints cap results with limit and page by createdAt rather
than an offset:
def all_assistants(client, page_size=100):
cursor = None
while True:
page = client.assistants.list(limit=page_size, createdAtLt=cursor)
if not page:
break
yield from page
cursor = page[-1]["createdAt"]
if len(page) < page_size:
breakfile = client.files.create(path="./knowledge.pdf")import asyncio
from vapi_agent import AsyncVapiAgent
async def main():
async with AsyncVapiAgent(api_key="your-key") as client:
assistants = await client.assistants.list(limit=20)
print(len(assistants))
asyncio.run(main())result = client.analytics.query([
{
"name": "calls_by_day",
"table": "call",
"timeRange": {"step": "day"},
"operations": [{"operation": "count", "column": "id"}],
}
])Non-2xx responses raise a typed exception (all subclasses of VapiAPIError):
from vapi_agent import NotFoundError, AuthenticationError, VapiAPIError
try:
client.assistants.get("does-not-exist")
except NotFoundError:
print("no such assistant")
except AuthenticationError:
print("check your API key")
except VapiAPIError as e:
print(e.status_code, e.body)client = VapiAgent(
api_key="your-key",
base_url="https://api.vapi.ai", # override for self-hosted/proxy
timeout=60.0,
default_headers={"X-My-Header": "value"},
)Both clients support context managers (with / async with) and expose
close() / aclose() for manual cleanup.
MIT