Python SDK for the Teardrop AI agent API
Project description
teardrop-sdk
Python SDK for the Teardrop AI agent API.
Requirements
- Python ≥ 3.11
httpx >= 0.28,pydantic >= 2.10,anyio >= 4.0
Install
pip install teardrop-sdk
Quick Start
import asyncio
from teardrop import AsyncTeardropClient
async def main():
async with AsyncTeardropClient(
"https://api.teardrop.dev",
email="you@example.com",
secret="your-password",
) as client:
async for event in client.run("What is the ETH price on Base?"):
if event.type == "TEXT_MESSAGE_CONTENT":
print(event.data.get("delta", ""), end="", flush=True)
print()
asyncio.run(main())
Sync Usage
from teardrop import TeardropClient
with TeardropClient(
"https://api.teardrop.dev",
email="you@example.com",
secret="your-password",
) as client:
for event in client.run_sync("What is 2 + 2?"):
if event.type == "TEXT_MESSAGE_CONTENT":
print(event.data.get("delta", ""), end="", flush=True)
print()
TeardropClient is a thin synchronous wrapper around AsyncTeardropClient. Every async method on the async client has an identical sync counterpart, except run() → run_sync().
Authentication
Credentials are passed to the constructor. The TokenManager acquires a JWT automatically on the first request and refreshes it before expiry (30-minute window).
| Method | Constructor kwargs |
|---|---|
| Email + password | email=..., secret=... |
| Client credentials (M2M) | client_id=..., client_secret=... |
| Pre-authenticated static token | token=... |
| SIWE (sign-in with Ethereum) | Call authenticate_siwe() after construction (see below) |
SIWE Login Flow
async with AsyncTeardropClient("https://api.teardrop.dev") as client:
# 1. Fetch a single-use nonce
nonce_resp = await client.get_siwe_nonce()
nonce = nonce_resp["nonce"]
# 2. Build and sign an EIP-4361 message client-side (e.g. with siwe-py)
message = build_siwe_message(nonce=nonce, ...)
signature = wallet.sign_message(message)
# 3. Exchange for a JWT — stored automatically for subsequent calls
token = await client.authenticate_siwe(message, signature, nonce)
Inspect Identity
me = await client.get_me()
# → JwtPayloadBase(sub=..., org_id=..., role="member", auth_method="email", ...)
Agent Runs
async for event in client.run(
"Summarise the top DeFi news today",
thread_id="conv-abc123", # optional; uuid generated if omitted
model="claude-opus-4-5", # optional LLM override
):
print(event.type, event.data)
run() is an async generator that yields SSEEvent objects. The sync equivalent run_sync() blocks and returns list[SSEEvent].
SSEEvent
class SSEEvent:
type: str # see event type constants below
data: dict[str, Any]
id: str # SSE stream ID (for resumption)
retry: int | None # retry interval in ms, if set by server
Event Types
event.type |
event.data keys |
Notes |
|---|---|---|
RUN_STARTED |
run_id, thread_id |
First event of every run |
TEXT_MESSAGE_CONTENT |
delta |
Streaming text chunk |
TOOL_CALL_START |
tool_call_id, tool_name, args |
Agent is calling a tool |
TOOL_CALL_END |
tool_call_id, result |
Tool returned |
SURFACE_UPDATE |
surface, content |
UI surface payload |
USAGE_SUMMARY |
tokens_in, tokens_out, tool_calls |
Per-run token usage |
BILLING_SETTLEMENT |
run_id, cost_usdc |
Credit deducted |
ERROR |
message, code |
Non-fatal error during run |
DONE |
(empty) | Stream complete |
x402 Payments
If the agent returns a 402 Payment Required the SDK raises PaymentRequiredError. Resolve the payment externally and retry with:
async for event in client.run(
prompt,
x402_payment_header="...", # value from 402 response
payment_signature="...", # EIP-3009 signature
):
...
Error Handling
All exceptions inherit from TeardropError.
from teardrop.exceptions import (
TeardropError,
AuthenticationError, # 401
PaymentRequiredError, # 402 — .requirements dict attached
ForbiddenError, # 403
NotFoundError, # 404
ConflictError, # 409
ValidationError, # 422
RateLimitError, # 429 — .retry_after (seconds)
GatewayError, # 502 / 504
APIError, # all other non-2xx
)
from teardrop.exceptions import RateLimitError, PaymentRequiredError
import asyncio
try:
async for event in client.run("..."):
...
except RateLimitError as e:
await asyncio.sleep(e.retry_after)
except PaymentRequiredError as e:
print("x402 requirements:", e.requirements)
Billing
Balance
balance = await client.get_balance()
# → BillingBalance(org_id=..., balance_usdc=5000, reserved_usdc=200, available_usdc=4800)
USDC amounts are in atomic units (micro-USDC, 6 decimals). Divide by 1_000_000 for human-readable USDC.
Pricing
pricing = await client.get_pricing() # no auth required
for tool in pricing.tools:
print(tool.tool_name, tool.price_usdc)
Billing History
page = await client.get_billing_history(limit=50)
entries: list[BillingHistoryEntry] = page["items"]
cursor = page.get("next_cursor") # pass to next call to paginate
Invoices
# Paginated list
page = await client.get_invoices(limit=20, cursor=cursor)
# Single run invoice
invoice = await client.get_invoice(run_id)
# → Invoice(run_id=..., tokens_in=..., tokens_out=..., tool_calls=..., total_usdc=..., settled_at=...)
Credit History
page = await client.get_credit_history()
entries: list[CreditHistoryEntry] = page["items"]
Stripe Top-up
from teardrop import StripeTopupRequest
resp = await client.topup_stripe(StripeTopupRequest(
amount_usdc=10_000_000, # 10 USDC
success_url="https://app.example.com/billing?success=1",
cancel_url="https://app.example.com/billing",
))
# Redirect user to resp.checkout_url
# Poll for completion
status = await client.get_stripe_topup_status(resp.session_id)
# → StripeTopupStatusResponse(status="complete"|"open"|"expired", amount_usdc=...)
USDC Top-up (on-chain EIP-3009)
from teardrop import UsdcTopupRequest
reqs = await client.get_usdc_topup_requirements()
# → UsdcTopupRequirements(payto_address=..., token_address=..., network=..., chain_id=...,
# min_amount_usdc=..., authorization_type="EIP-3009")
result = await client.topup_usdc(UsdcTopupRequest(
amount_usdc=5_000_000,
authorization="...", # EIP-3009 signed authorization
signature="...",
tx_hash="0x...", # optional
))
# → {"credited_usdc": 5000000}
Usage Summary
summary = await client.get_usage(from_date="2026-04-01", to_date="2026-04-30")
# → UsageSummary(total_runs=..., total_tokens_in=..., total_tokens_out=...,
# total_tool_calls=..., total_cost_usdc=...)
Wallets
Link Ethereum wallets to a user account for USDC payments and SIWE authentication.
from teardrop import LinkWalletRequest
# nonce + message + signature follow same SIWE pattern as login
wallet = await client.link_wallet(LinkWalletRequest(
message="...",
signature="...",
nonce="...",
))
wallets: list[Wallet] = await client.get_wallets()
await client.delete_wallet(wallet.id)
Org Webhook Tools
Register custom webhook-backed tools that the agent can call during runs.
from teardrop import CreateOrgToolRequest, UpdateOrgToolRequest
# Register
tool = await client.create_tool(CreateOrgToolRequest(
name="send_email", # lowercase, a-z0-9_
description="Send an email via Sendgrid",
input_schema={ # JSON Schema object
"type": "object",
"properties": {
"to": {"type": "string"},
"subject": {"type": "string"},
"body": {"type": "string"},
},
"required": ["to", "subject", "body"],
},
webhook_url="https://hooks.example.com/email",
webhook_secret="whsec_...", # optional HMAC secret
))
tools: list[OrgTool] = await client.list_tools()
tool = await client.get_tool(tool.id)
# Partial update — only provided fields are sent
updated = await client.update_tool(tool.id, UpdateOrgToolRequest(
description="Send email via AWS SES",
is_active=False,
))
await client.delete_tool(tool.id)
MCP Servers
Register external MCP (Model Context Protocol) servers. The agent auto-discovers their tools at run time and namespaces them as {server_name}__{tool_name}.
from teardrop import CreateMcpServerRequest, UpdateMcpServerRequest, parse_mcp_tool_name
# Register
server = await client.create_mcp_server(CreateMcpServerRequest(
name="stripe", # becomes tool prefix
url="https://your-stripe-mcp.example.com/sse",
auth_type="bearer", # "none" | "bearer" | "header"
auth_token="sk-...", # write-only; never returned
timeout_seconds=15,
))
servers: list[OrgMcpServer] = await client.list_mcp_servers()
server = await client.get_mcp_server(server.id)
# Partial update
await client.update_mcp_server(server.id, UpdateMcpServerRequest(
auth_token="sk-new-...",
timeout_seconds=30,
))
# Live probe — bypasses agent TTL cache, does not mutate state
discovery = await client.discover_mcp_server_tools(server.id)
for tool in discovery.tools:
print(tool.name, tool.description)
await client.delete_mcp_server(server.id)
MCP Tool Names in Events
async for event in client.run("Issue a refund for ch_abc123"):
if event.type == "TOOL_CALL_START":
parsed = parse_mcp_tool_name(event.data["tool_name"])
if parsed["is_mcp"]:
print(f"MCP → {parsed['server']}.{parsed['tool']}")
parse_mcp_tool_name("stripe__create_refund")
# → {"is_mcp": True, "server": "stripe", "tool": "create_refund"}
parse_mcp_tool_name("web_search")
# → {"is_mcp": False}
MCP Behavioural Notes
| Constraint | Detail |
|---|---|
| Quota | 5 active servers per org by default; 422 on breach |
| Cache lag | New/updated servers are live within ~5 min (TTL 300 s); /discover bypasses cache |
| Auth write-only | auth_token is write-only; only has_auth: bool is returned |
| Transport | Streamable HTTP only — stdio MCP servers are not supported |
| SSRF | Server-side URL validation blocks private IPs and localhost |
Memory
Store and retrieve persistent memory entries scoped to the org. The agent can read these during runs.
from teardrop import StoreMemoryRequest
entry = await client.create_memory(StoreMemoryRequest(
content="User prefers responses in Spanish.",
metadata={"user_id": "usr_123"},
ttl_seconds=86400, # optional TTL; omit for persistent
))
page = await client.list_memories(limit=50)
entries: list[MemoryEntry] = page.items
await client.delete_memory(entry.id)
Marketplace
Browse, publish, and monetise tools on the Teardrop marketplace.
Browsing (no auth required)
page = await client.get_marketplace_catalog(limit=20, tags=["defi", "payments"])
tools: list[MarketplaceTool] = page["items"]
Author Configuration
config = await client.set_author_config(payout_address="0xYourWallet")
config = await client.get_author_config()
# → AuthorConfig(org_id=..., payout_address=..., is_verified=...)
Earnings & Withdrawal
balance = await client.get_marketplace_balance()
# → {"balance_usdc": 1500000, ...}
page = await client.get_earnings(limit=50)
entries: list[EarningsEntry] = page["items"]
from teardrop import WithdrawRequest
result = await client.withdraw(WithdrawRequest(
amount_usdc=1_000_000,
payout_address="0xOptionalOverride", # omit to use author config address
))
Agent Card
Fetch the A2A agent card from /.well-known/agent-card.json. Result is cached for 5 minutes.
card = await client.get_agent_card()
# → AgentCard(name=..., description=..., url=..., skills=[...])
# Bypass cache
card = await client.get_agent_card(force_refresh=True)
Alternatively, create a client and pre-warm the cache atomically:
client = await AsyncTeardropClient.from_agent_card("https://api.teardrop.dev", email="...", secret="...")
Models Reference
All request/response types are Pydantic v2 models exported from teardrop.
| Model | Used by |
|---|---|
JwtPayloadBase |
get_me() |
AgentRunRequest |
run() (internal) |
SSEEvent |
run() yields |
BillingBalance |
get_balance() |
BillingPricingResponse, ToolPricing |
get_pricing() |
BillingHistoryEntry |
get_billing_history() |
Invoice |
get_invoices(), get_invoice() |
CreditHistoryEntry |
get_credit_history() |
StripeTopupRequest, StripeTopupResponse, StripeTopupStatusResponse |
topup_stripe(), get_stripe_topup_status() |
UsdcTopupRequirements, UsdcTopupRequest |
get_usdc_topup_requirements(), topup_usdc() |
UsageSummary |
get_usage() |
Wallet, LinkWalletRequest |
get_wallets(), link_wallet() |
AgentCard |
get_agent_card() |
OrgTool, CreateOrgToolRequest, UpdateOrgToolRequest |
tool CRUD |
OrgMcpServer, CreateMcpServerRequest, UpdateMcpServerRequest, DiscoverMcpToolsResponse, McpToolDefinition |
MCP CRUD |
MemoryEntry, MemoryListResponse, StoreMemoryRequest |
memory CRUD |
MarketplaceTool, AuthorConfig, EarningsEntry, WithdrawRequest |
marketplace |
Import any model directly:
from teardrop import CreateMcpServerRequest, OrgTool, BillingBalance
Development
# install dev deps
pip install -e ".[dev]"
# run tests
pytest
# run tests with coverage
pytest --cov=teardrop --cov-report=term-missing
112 tests covering client methods, streaming, auth, and models.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file teardrop_sdk-0.1.2.tar.gz.
File metadata
- Download URL: teardrop_sdk-0.1.2.tar.gz
- Upload date:
- Size: 29.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2d4eda93366878565ffd02d88082f402af4b286324852092cb54ace93cba0c33
|
|
| MD5 |
177734a9bd9ee9337c458187f4f1c937
|
|
| BLAKE2b-256 |
7be8c1c73417612c96778b4ce64721919d823ecbeaacd5a019401f1c9a56cb1e
|
File details
Details for the file teardrop_sdk-0.1.2-py3-none-any.whl.
File metadata
- Download URL: teardrop_sdk-0.1.2-py3-none-any.whl
- Upload date:
- Size: 23.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ae6c8299a552091f2b4ec3a48f6d911c51c4e18fd8b688ec22469708d74fb68
|
|
| MD5 |
8798d41675223fbdf231d6e774a1f6af
|
|
| BLAKE2b-256 |
98de5677459a822e5434a31bb1ce63482d18702bdddf7dc81337c057cbf96316
|