Skip to main content

Official Python SDK for Tether.name - AI agent identity verification

Project description

Tether.name Python SDK

PyPI Python versions License: MIT

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:

  1. 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
  2. Sign: Your agent signs a cryptographic challenge using its private key
  3. 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), or
  • challenge + proof signed 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:

  1. pyproject.toml โ†’ version
  2. src/tether_name/__init__.py โ†’ __version__

Steps

  1. Update version numbers above (they must match)
  2. Commit and push to main
  3. Create a GitHub release with a matching tag (e.g. v1.0.0)
  4. 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


Ready to get started? Register your AI agent at tether.name and start building with cryptographic identity verification! ๐Ÿš€

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

tether_name-2.0.4.tar.gz (18.0 kB view details)

Uploaded Source

Built Distribution

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

tether_name-2.0.4-py3-none-any.whl (14.7 kB view details)

Uploaded Python 3

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

Hashes for tether_name-2.0.4.tar.gz
Algorithm Hash digest
SHA256 cec2333d9c195e0086daa801adbbd21ae9f96f29692adb6ab6fa31524e8a69c8
MD5 d5104d0e70944604343623e54f9dd361
BLAKE2b-256 cc112ad18ccd093c26b87d69376e8e23d0ee92e85d5ae76dd7acc6b5b0f6641c

See more details on using hashes here.

Provenance

The following attestation bundles were made for tether_name-2.0.4.tar.gz:

Publisher: publish.yml on tether-name/tether-name-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for tether_name-2.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 8e5ef78149c967585256315b453aa1cb085a0028154f3557f8968e9b288eead3
MD5 7cf021ac650a44a813a9b60586efadc5
BLAKE2b-256 a1bf24964bdd7f6ce4b3aa18b9d0ee0aaa85062015eac57d10a4640c546e7e03

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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