Python SDK
A typed Python client for the operations an integration runs server-side — provisioning customers, managing agents, and streaming chat.
Who can do what
| Task | User | Agent owner | Admin |
|---|---|---|---|
| Use the SDK with their own key | ✅ | ✅ | ✅ |
| Use the provisioning and admin routes it wraps | — | — | ✅ |
Installing
No PyPI release yet; install from git:
pip install "git+https://github.com/VerinFast/dolly.git#subdirectory=sdks/python"
Authenticating
The client takes an admin user API key, sent as X-Api-Key. Two constraints,
both of which bite if ignored:
- It must be a user key. The agent and provisioning routes need a real user behind the request.
- That user must be a tenant admin. Provisioning and admin routes are gated on the role.
Mint it once in the browser — key creation needs a sign-in — and keep it server-side. It is effectively a master key for your organization: it belongs on your backend and nowhere near a browser. To put an agent in front of the public, use a share link instead.
See API keys.
Provisioning a customer
The method most integrations reach for. One call creates the customer's agent, attaches your registered tools with that customer's credentials, and discovers each MCP server's catalogue:
from dolly_sdk import DollyClient
dolly = DollyClient(api_key, "https://agent.verinfast.com")
result = dolly.provisioning.create_customer(
tenant_id,
external_id="customer_42",
agent={"name": "Customer 42's agent"},
)
# Store result's agent id against your customer — it's how you route requests.
See Provisioning customers for the full
shape, including the rejected list you should be reading.
Agents
agent = dolly.agents.create(tenant_id, "An agent", instructions="…")
dolly.agents.get(agent.id)
dolly.agents.list(tenant_id)
dolly.agents.delete(agent.id) # also releases the sandbox and purges the workspace
Streaming a conversation
async for event in client.chat.stream(
agent_id,
"Show me pictures of Sean in the winter",
session_id=session_id,
attachments=[Attachment.from_path("undated.jpg")],
mode="plan", # propose without acting; "go" to proceed
):
...
mode is the same Plan / Go control the app offers.
Building your UI without a live agent
The SDK ships a fake transport that replays a scripted event stream through a real client — same events, same iteration — so you can build and test your chat UI with no agent, no network, and no token spend:
from dolly_sdk import AsyncDollyClient
from dolly_sdk.testing import fake_chat_transport
transport = fake_chat_transport([
{"type": "tool_call", "data": {"name": "search_photos"}},
{"type": "message", "data": {"content": "Found 3 photos."}},
{"type": "task_completed", "data": {"session_id": "ses_1"}},
])
client = AsyncDollyClient("uak_x", "https://agent.test", transport=transport)
For tests, the client takes any httpx transport, so httpx.MockTransport
works directly.
Errors
Every failure is a DollyError subclass, with the API's own reason on
.detail:
from dolly_sdk import DollyConflictError
try:
dolly.agents.create(tenant_id, "dup")
except DollyConflictError as exc:
print(exc.status, exc.detail) # 409, "A tool named 'dup' already exists"
DollyAuthError (401/403), DollyConflictError (409), DollyNotFoundError
(404), and DollyAPIError for anything else.
Some admin routes deliberately answer 404 rather than 403, so existence isn't disclosed to a caller who lacks access. Don't treat a 404 as proof something doesn't exist.
Not yet covered
Managing the tool registry from the SDK — registering MCP servers, editing the egress allow-list — isn't in this release. Use Organization → Integrations or the custom-tools API meanwhile. See Custom tools.