Tether.name Python SDK
Official Python SDK for Tether.name — cryptographic identity verification for AI agents.
Tether lets AI agents prove their identity using RSA-2048 digital signatures, providing a secure, verifiable way to establish trust in AI-to-AI and AI-to-human interactions.
🚀 Quick Start
Installation
pip install tether-name
Basic Usage
from tether_name import TetherClient
# Initialize with your agent
client = TetherClient(
agent_id="your-agent-id",
private_key_path="/path/to/your/private-key.pem"
)
# Verify your agent's identity
result = client.verify()
if result.verified:
print(f"✅ Verified as: {result.agent_name}")
print(f"📧 Email: {result.email}")
print(f"🔗 Verification URL: {result.verify_url}")
else:
print(f"❌ Verification failed: {result.error}")
Agent Management
One line to start managing agents programmatically:
from tether_name import TetherClient
client = TetherClient(api_key="sk-tether-name-...")
# Create, list, and delete agents
agent = client.create_agent("my-bot", domain_id="verified-domain-id")
# Switch verification display to account email
client.update_agent_domain(agent.id, "")
# Or switch to a verified domain
client.update_agent_domain(agent.id, "verified-domain-id")
agents = client.list_agents()
domains = client.list_domains()
client.delete_agent(agent.id)
# Key lifecycle operations
keys = client.list_agent_keys(agent.id)
rotated = client.rotate_agent_key(
agent.id,
public_key="BASE64_SPKI_PUBLIC_KEY",
grace_period_hours=24,
reason="routine_rotation",
step_up_code="123456", # or challenge+proof
)
client.revoke_agent_key(
agent.id,
rotated.new_key_id,
reason="compromised",
step_up_code="654321", # or challenge+proof
)
📖 How Tether Works
Tether.name provides cryptographic identity verification for AI agents through a simple 3-step process:
- Register: Create an agent identity at tether.name to get your agent ID, then generate your RSA-2048 keypair locally and register the public key
- Sign: Your agent signs a cryptographic challenge using its private key
- Verify: The signature proves your agent's identity to others
This creates unforgeable digital identity that anyone can verify.
🔧 Configuration
Authentication
The SDK supports two authentication modes:
Bearer token — for management operations (JWT or API key):
client = TetherClient(api_key="sk-tether-name-...")
Private Key — for identity verification (sign, verify):
client = TetherClient(
agent_id="your-agent-id",
private_key_path="/path/to/key.pem"
)
Both — for full access:
client = TetherClient(
api_key="sk-tether-name-...",
agent_id="your-agent-id",
private_key_path="/path/to/key.pem"
)
Environment Variables
Set these environment variables to avoid hardcoding secrets:
export TETHER_API_KEY="sk-tether-name-..."
export TETHER_AGENT_ID="your-agent-id"
export TETHER_PRIVATE_KEY_PATH="/path/to/your/key.pem"
Then initialize without parameters:
client = TetherClient() # Uses environment variables
Key Formats
The SDK supports multiple private key formats:
# From file path (PEM or DER)
client = TetherClient(
agent_id="...",
private_key_path="/path/to/key.pem"
)
# From PEM string
client = TetherClient(
agent_id="...",
private_key_pem="-----BEGIN PRIVATE KEY-----\n..."
)
# From DER bytes
with open("key.der", "rb") as f:
key_bytes = f.read()
client = TetherClient(
agent_id="...",
private_key_der=key_bytes
)
📚 API Reference
TetherClient
Main client for Tether.name API interactions.
Constructor
TetherClient(
agent_id: Optional[str] = None,
private_key_path: Optional[Union[str, Path]] = None,
private_key_pem: Optional[Union[str, bytes]] = None,
private_key_der: Optional[bytes] = None,
timeout: float = 30.0,
api_key: Optional[str] = None
)
| Parameter | Env var | Description |
|---|---|---|
api_key |
TETHER_API_KEY |
Management bearer token (API key or JWT) |
agent_id |
TETHER_AGENT_ID |
Agent ID for identity verification |
private_key_path |
TETHER_PRIVATE_KEY_PATH |
Path to RSA-2048 private key (PEM or DER) |
private_key_pem |
— | PEM-encoded private key string |
private_key_der |
— | DER-encoded private key bytes |
When api_key is set, agent_id and private key parameters become optional (only needed for verify/sign operations).
Methods
verify() -> VerificationResult
Perform complete identity verification in one call.
result = client.verify()
print(result.verified) # bool: True if verified
print(result.agent_name) # str: Your agent's display name
print(result.verify_url) # str: Public verification URL
print(result.email) # str: Registered email address
request_challenge() -> str
Request a cryptographic challenge from Tether.
challenge = client.request_challenge()
print(challenge) # "550e8400-e29b-41d4-a716-446655440000"
sign(challenge: str) -> str
Sign a challenge with your private key.
challenge = client.request_challenge()
signature = client.sign(challenge)
print(signature) # URL-safe base64 signature (no padding)
submit_proof(challenge: str, proof: str) -> VerificationResult
Submit signed challenge for verification.
challenge = client.request_challenge()
signature = client.sign(challenge)
result = client.submit_proof(challenge, signature)
create_agent(agent_name: str, description: str = "", domain_id: str = "") -> Agent
Create a new agent. Requires bearer auth (JWT or API key). domain_id is optional and assigns this agent to a verified domain.
agent = client.create_agent("my-bot", description="My automated agent", domain_id="verified-domain-id")
print(agent.id) # Agent ID
print(agent.agent_name) # "my-bot"
print(agent.registration_token) # Token for agent registration
list_agents() -> list[Agent]
List all agents. Requires bearer auth (JWT or API key).
agents = client.list_agents()
for agent in agents:
print(f"{agent.agent_name} (created {agent.created_at})")
list_domains() -> list[Domain]
List all registered domains for the account. Requires bearer auth (JWT or API key).
domains = client.list_domains()
for domain in domains:
print(domain.domain, domain.verified)
delete_agent(agent_id: str) -> bool
Delete an agent. Requires bearer auth (JWT or API key).
client.delete_agent("agent-id-here")
update_agent_domain(agent_id: str, domain_id: str = "") -> UpdateAgentResult
Update which identity is shown when an agent is verified. Pass a verified domain_id to show that domain, or pass an empty string ("") to show account email.
client.update_agent_domain("agent-id-here", "verified-domain-id")
client.update_agent_domain("agent-id-here", "") # show account email
list_agent_keys(agent_id: str) -> list[AgentKey]
List key lifecycle entries (active, grace, revoked) for an agent. Requires bearer auth (JWT or API key).
rotate_agent_key(...) -> RotateKeyResult
Rotate an agent key. Requires bearer auth (JWT or API key) plus step-up verification via either:
step_up_code(email code), orchallenge+proofsigned by a current key.
result = client.rotate_agent_key(
"agent-id",
public_key="BASE64_SPKI_PUBLIC_KEY",
grace_period_hours=24,
step_up_code="123456",
)
revoke_agent_key(...) -> RevokeKeyResult
Revoke an agent key with the same step-up requirements as rotate.
result = client.revoke_agent_key(
"agent-id",
"key-id",
reason="compromised",
step_up_code="123456",
)
Agent
Agent object returned by management operations.
@dataclass
class Agent:
id: str # Unique agent ID
agent_name: str # Agent display name
description: str # Agent description
domain_id: str = "" # Optional assigned domain ID
domain: Optional[str] = None # Resolved domain name
created_at: int # Creation time (epoch ms)
registration_token: str = "" # Token for agent registration
last_verified_at: int = 0 # Last verification time (epoch ms)
@dataclass
class Domain:
id: str
domain: str
verified: bool
verified_at: int = 0
last_checked_at: int = 0
created_at: int = 0
@dataclass
class AgentKey:
id: str
status: str
created_at: int
activated_at: int
grace_until: int
revoked_at: int
revoked_reason: str = ""
VerificationResult
Result object returned by verification operations.
@dataclass
class VerificationResult:
verified: bool # True if verification succeeded
agent_name: Optional[str] = None # Agent's display name
verify_url: Optional[str] = None # Public verification URL
email: Optional[str] = None # Registered email
domain: Optional[str] = None # Verified domain (if assigned)
registered_since: Optional[datetime] = None # Registration date
error: Optional[str] = None # Error message if failed
challenge: Optional[str] = None # Original challenge
🔐 Step-by-Step Example
For more control, you can break down the verification process:
from tether_name import TetherClient, TetherAPIError, TetherVerificationError
try:
client = TetherClient(
agent_id="your-agent-id",
private_key_path="/path/to/key.pem"
)
# Step 1: Request a challenge
print("📡 Requesting challenge...")
challenge = client.request_challenge()
print(f"🔢 Challenge: {challenge}")
# Step 2: Sign the challenge
print("✍️ Signing challenge...")
signature = client.sign(challenge)
print(f"📝 Signature: {signature[:32]}...")
# Step 3: Submit proof
print("📤 Submitting proof...")
result = client.submit_proof(challenge, signature)
if result.verified:
print(f"✅ Successfully verified as {result.agent_name}")
print(f"🔗 Share this verification: {result.verify_url}")
else:
print(f"❌ Verification failed: {result.error}")
except TetherAPIError as e:
print(f"🌐 API Error: {e.message}")
if e.status_code:
print(f"📊 Status: {e.status_code}")
except TetherVerificationError as e:
print(f"🔒 Verification Error: {e.message}")
finally:
client.close() # Clean up HTTP connections
🧪 Testing
The SDK includes comprehensive unit tests that don't hit the live API:
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Run with coverage
pytest --cov=tether_name
🔗 Context Manager Support
Use TetherClient as a context manager for automatic cleanup:
with TetherClient(agent_id="...", private_key_path="...") as client:
result = client.verify()
print(f"Verified: {result.verified}")
# HTTP client automatically closed
🛡️ Security Notes
- Private Key Security: Never commit private keys to version control or share them publicly
- API Key Security: API keys are hashed before storage. The
sk-tether-name-prefix enables leak detection. Revoke compromised keys immediately - Key Format: Tether requires RSA-2048 keys. Other key sizes will be rejected
- Challenge Uniqueness: Each verification uses a unique challenge to prevent replay attacks
- Signature Algorithm: Uses SHA256withRSA (PKCS#1 v1.5 padding) as specified by Tether
🐛 Error Handling
The SDK provides specific exception types for different error conditions:
from tether_name import (
TetherError, # Base exception
TetherAPIError, # API request failures
TetherVerificationError, # Verification failures
TetherKeyError, # Private key issues
)
try:
result = client.verify()
except TetherAPIError as e:
# Handle API connectivity or server errors
print(f"API Error {e.status_code}: {e.message}")
except TetherVerificationError as e:
# Handle verification failures (invalid signature, etc.)
print(f"Verification failed: {e.message}")
except TetherKeyError as e:
# Handle private key loading or format errors
print(f"Key error: {e.message}")
except TetherError as e:
# Handle any other Tether-related errors
print(f"Tether error: {e.message}")
📋 Requirements
- Python: 3.8+
- Dependencies:
httpx>=0.20.0,cryptography>=3.4.0 - Key Format: RSA-2048 private key (PEM or DER)
📦 Publishing
Published to PyPI automatically via GitHub Actions when a release is created (uses trusted publishing).
Version checklist
Update the version in:
pyproject.toml→versionsrc/tether_name/__init__.py→__version__
Steps
- Update version numbers above (they must match)
- Commit and push to
main - Create a GitHub release with a matching tag (e.g.
v1.0.0) - CI builds and publishes to PyPI automatically
Manual publish (if needed)
pip install build twine
python -m build
twine upload dist/*
📄 License
MIT License - see LICENSE file for details.
🤝 Contributing
Contributions welcome! Please see the GitHub repository for details.
🔗 Links
- 🌐 Tether.name: https://tether.name
- 📦 PyPI Package: https://pypi.org/project/tether-name/
- 💻 Source Code: https://github.com/tether-name/tether-name-python
- 📖 API Documentation: https://docs.tether.name
- ❓ Support: security@tether.name
Ready to get started? Register your AI agent at tether.name and start building with cryptographic identity verification! 🚀
Metadata
Release files for tether-name 2.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tether_name-2.0.4.tar.gz | 18.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tether_name-2.0.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.7 kB
Release files / tether_name-2.0.4.tar.gz
| Download URL | tether_name-2.0.4.tar.gz |
|---|---|
| Size | 18.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cec2333d9c195e0086daa801adbbd21ae9f96f29692adb6ab6fa31524e8a69c8
|
|
BLAKE2b-256 checksum How to use checksums |
cc112ad18ccd093c26b87d69376e8e23d0ee92e85d5ae76dd7acc6b5b0f6641c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 4, 2026.
Transparency logRelease files / tether_name-2.0.4-py3-none-any.whl
| Download URL | tether_name-2.0.4-py3-none-any.whl |
|---|---|
| Size | 14.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8e5ef78149c967585256315b453aa1cb085a0028154f3557f8968e9b288eead3
|
|
BLAKE2b-256 checksum How to use checksums |
a1bf24964bdd7f6ce4b3aa18b9d0ee0aaa85062015eac57d10a4640c546e7e03
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 4, 2026.
Transparency log