Official Python SDK for Tether.name - AI agent identity verification
Project description
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! ๐
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 tether_name-2.0.4.tar.gz.
File metadata
- Download URL: tether_name-2.0.4.tar.gz
- Upload date:
- Size: 18.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cec2333d9c195e0086daa801adbbd21ae9f96f29692adb6ab6fa31524e8a69c8
|
|
| MD5 |
d5104d0e70944604343623e54f9dd361
|
|
| BLAKE2b-256 |
cc112ad18ccd093c26b87d69376e8e23d0ee92e85d5ae76dd7acc6b5b0f6641c
|
Provenance
The following attestation bundles were made for tether_name-2.0.4.tar.gz:
Publisher:
publish.yml on tether-name/tether-name-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tether_name-2.0.4.tar.gz -
Subject digest:
cec2333d9c195e0086daa801adbbd21ae9f96f29692adb6ab6fa31524e8a69c8 - Sigstore transparency entry: 1026775794
- Sigstore integration time:
-
Permalink:
tether-name/tether-name-python@364b0a17e75d86f7231d6c76352b285d850b4fc6 -
Branch / Tag:
refs/tags/v2.0.4 - Owner: https://github.com/tether-name
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@364b0a17e75d86f7231d6c76352b285d850b4fc6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tether_name-2.0.4-py3-none-any.whl.
File metadata
- Download URL: tether_name-2.0.4-py3-none-any.whl
- Upload date:
- Size: 14.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e5ef78149c967585256315b453aa1cb085a0028154f3557f8968e9b288eead3
|
|
| MD5 |
7cf021ac650a44a813a9b60586efadc5
|
|
| BLAKE2b-256 |
a1bf24964bdd7f6ce4b3aa18b9d0ee0aaa85062015eac57d10a4640c546e7e03
|
Provenance
The following attestation bundles were made for tether_name-2.0.4-py3-none-any.whl:
Publisher:
publish.yml on tether-name/tether-name-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tether_name-2.0.4-py3-none-any.whl -
Subject digest:
8e5ef78149c967585256315b453aa1cb085a0028154f3557f8968e9b288eead3 - Sigstore transparency entry: 1026775854
- Sigstore integration time:
-
Permalink:
tether-name/tether-name-python@364b0a17e75d86f7231d6c76352b285d850b4fc6 -
Branch / Tag:
refs/tags/v2.0.4 - Owner: https://github.com/tether-name
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@364b0a17e75d86f7231d6c76352b285d850b4fc6 -
Trigger Event:
push
-
Statement type: