Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

teardrop_sdk-0.1.2.tar.gz (29.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

teardrop_sdk-0.1.2-py3-none-any.whl (23.1 kB view details)

Uploaded Python 3

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

Hashes for teardrop_sdk-0.1.2.tar.gz
Algorithm Hash digest
SHA256 2d4eda93366878565ffd02d88082f402af4b286324852092cb54ace93cba0c33
MD5 177734a9bd9ee9337c458187f4f1c937
BLAKE2b-256 7be8c1c73417612c96778b4ce64721919d823ecbeaacd5a019401f1c9a56cb1e

See more details on using hashes here.

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

Hashes for teardrop_sdk-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7ae6c8299a552091f2b4ec3a48f6d911c51c4e18fd8b688ec22469708d74fb68
MD5 8798d41675223fbdf231d6e774a1f6af
BLAKE2b-256 98de5677459a822e5434a31bb1ce63482d18702bdddf7dc81337c057cbf96316

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page