tether-name
v2.0.4
Published
Official Node.js SDK for tether.name - AI agent identity verification
Maintainers
Readme
tether-name
Official Node.js SDK for tether.name — cryptographic identity verification for AI agents. Tether lets AI agents prove their identity using RSA-2048 signatures, enabling trusted agent-to-agent communication.
Installation
npm install tether-nameRequires Node.js 20+ (uses native fetch and crypto modules).
Quick Start
import { TetherClient } from 'tether-name';
const client = new TetherClient({
agentId: 'your-agent-id',
privateKeyPath: '/path/to/your/private-key.pem'
});
// One-call verification
const result = await client.verify();
console.log(result.verified); // true
console.log(result.agentName); // "Jawnnybot"
console.log(result.verifyUrl); // "https://tether.name/check?challenge=..."Step-by-Step Usage
For more control over the verification process:
import { TetherClient } from 'tether-name';
const client = new TetherClient({
agentId: 'your-agent-id',
privateKeyPem: `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----`
});
try {
// 1. Request a challenge from Tether
const challenge = await client.requestChallenge();
// 2. Sign the challenge with your private key
const proof = client.sign(challenge);
// 3. Submit the proof for verification
const result = await client.submitProof(challenge, proof);
if (result.verified) {
console.log(`✅ Verified as ${result.agentName}`);
console.log(`📝 Public verification: ${result.verifyUrl}`);
} else {
console.log(`❌ Verification failed: ${result.error}`);
}
} catch (error) {
console.error('Verification error:', error.message);
}Configuration Options
Constructor Options
interface TetherClientConfig {
// Management bearer token (API key or JWT)
apiKey?: string; // Or use TETHER_API_KEY env var
// Agent ID (required for verify/sign, optional with apiKey)
agentId?: string; // Or use TETHER_AGENT_ID env var
// Private key (required for verify/sign, choose one)
privateKeyPath?: string; // Path to DER or PEM file
privateKeyPem?: string; // PEM string directly
privateKeyBuffer?: Buffer; // DER buffer directly
// Optional
}Authentication Modes
Bearer token only — manage agents without a private key:
const client = new TetherClient({
apiKey: 'sk-tether-name-...'
});
const agent = await client.createAgent('my-bot');Bearer token + agent + private key — full access (management and verification):
const client = new TetherClient({
apiKey: 'sk-tether-name-...',
agentId: 'your-agent-id',
privateKeyPath: '/path/to/key.pem'
});Agent + private key only — verification without agent management (original behavior):
const client = new TetherClient({
agentId: 'your-agent-id',
privateKeyPath: '/path/to/key.pem'
});Key Format Support
The SDK supports both PEM and DER private key formats:
// From file path (auto-detects format)
const client1 = new TetherClient({
agentId: 'your-id',
privateKeyPath: '/path/to/key.pem' // or .der
});
// From PEM string
const client2 = new TetherClient({
agentId: 'your-id',
privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\n...'
});
// From DER buffer
const derBuffer = fs.readFileSync('/path/to/key.der');
const client3 = new TetherClient({
agentId: 'your-id',
privateKeyBuffer: derBuffer
});Agent Management
Create and manage agents programmatically with a bearer token:
const client = new TetherClient({ apiKey: 'sk-tether-name-...' });
// Create an agent
const agent = await client.createAgent('my-bot', 'Does helpful things', 'verified-domain-id');
console.log(agent.id); // "abc123"
console.log(agent.agentName); // "my-bot"
console.log(agent.registrationToken); // Use to register the agent
// List all agents
const agents = await client.listAgents();
// List registered domains
const domains = await client.listDomains();
// Update which identity shows on verification
// Pass a verified domain ID to show that domain:
await client.updateAgentDomain(agent.id, 'verified-domain-id');
// Or pass empty string to show account email:
await client.updateAgentDomain(agent.id, '');
// Delete an agent
await client.deleteAgent(agent.id);
// List key lifecycle entries for an agent
const keys = await client.listAgentKeys(agent.id);
// Rotate key (requires step-up via email code OR challenge+proof)
const rotated = await client.rotateAgentKey(agent.id, {
publicKey: 'BASE64_SPKI_PUBLIC_KEY',
gracePeriodHours: 24,
reason: 'routine_rotation',
stepUpCode: '123456',
});
// Revoke a key
const revoked = await client.revokeAgentKey(agent.id, rotated.newKeyId, {
reason: 'compromised',
stepUpCode: '654321',
});Environment Variables
Set these environment variables to avoid hardcoding secrets:
export TETHER_API_KEY="sk-tether-name-..." # Management bearer token (API key or JWT)
export TETHER_AGENT_ID="your-agent-id"
export TETHER_PRIVATE_KEY_PATH="/path/to/your/private-key.pem"Then initialize without parameters:
const client = new TetherClient({}); // Uses env varsAPI Reference
TetherClient
constructor(config: TetherClientConfig)
Creates a new Tether client instance.
async verify(): Promise<VerificationResult>
Performs complete verification in one call. Requests challenge, signs it, and submits proof.
Throws: TetherVerificationError if verification fails.
async requestChallenge(): Promise<string>
Requests a new challenge from the Tether API.
Returns: Challenge string to be signed.
sign(challenge: string): string
Signs a challenge using the configured private key.
Returns: URL-safe base64 signature (no padding).
async submitProof(challenge: string, proof: string): Promise<VerificationResult>
Submits signed proof to verify the challenge.
async createAgent(agentName: string, description?: string, domainId?: string): Promise<Agent>
Creates a new agent. Requires bearer auth (Authorization: Bearer ..., JWT or API key). domainId is optional and links this agent to a verified domain.
async listAgents(): Promise<Agent[]>
Lists all agents for the authenticated account. Requires bearer auth (JWT or API key).
async listDomains(): Promise<Domain[]>
Lists all registered domains for the authenticated account. Requires bearer auth (JWT or API key).
async updateAgentDomain(agentId: string, domainId?: string): Promise<UpdateAgentResponse>
Updates which identity is shown when an agent is verified. Pass a verified domainId to show that domain, or pass "" (empty string) to show the account email. Requires bearer auth (JWT or API key).
async deleteAgent(agentId: string): Promise<boolean>
Deletes an agent by ID. Requires bearer auth (JWT or API key). Returns true on success.
async listAgentKeys(agentId: string): Promise<AgentKey[]>
Lists key lifecycle entries (active, grace, revoked) for an agent. Requires bearer auth (JWT or API key).
async rotateAgentKey(agentId: string, request: RotateAgentKeyRequest): Promise<RotateAgentKeyResponse>
Rotates an agent key. Requires bearer auth (JWT or API key) plus step-up verification via either stepUpCode or challenge + proof.
async revokeAgentKey(agentId: string, keyId: string, request?: RevokeAgentKeyRequest): Promise<RevokeAgentKeyResponse>
Revokes an agent key. Requires bearer auth (JWT or API key) plus step-up verification via either stepUpCode or challenge + proof.
Types
interface VerificationResult {
verified: boolean; // Whether verification succeeded
agentName?: string; // Registered agent name
verifyUrl?: string; // Public verification URL
email?: string; // Registered email
domain?: string; // Verified domain (if assigned)
registeredSince?: string; // ISO date of registration
error?: string; // Error message if failed
challenge?: string; // The verified challenge
}interface Agent {
id: string; // Unique agent ID
agentName: string; // Agent display name
description: string; // Agent description
domainId?: string; // Optional assigned domain ID
domain?: string | null; // Resolved assigned domain name
createdAt: number; // Creation time (epoch ms)
registrationToken?: string; // Token for key registration (returned on create)
lastVerifiedAt?: number; // Last verification time (epoch ms)
}interface Domain {
id: string;
domain: string;
verified: boolean;
verifiedAt: number;
lastCheckedAt: number;
createdAt: number;
}interface AgentKey {
id: string;
status: 'active' | 'grace' | 'revoked';
createdAt: number;
activatedAt: number;
graceUntil: number;
revokedAt: number;
revokedReason: string;
}
interface RotateAgentKeyRequest {
publicKey: string;
gracePeriodHours?: number;
reason?: string;
stepUpCode?: string;
challenge?: string;
proof?: string;
}
interface RevokeAgentKeyRequest {
reason?: string;
stepUpCode?: string;
challenge?: string;
proof?: string;
}Errors
TetherError- Base error classTetherVerificationError- Verification failedTetherAPIError- API request failed
Getting Your Agent Identity
- Visit tether.name
- Register your agent and get an agent ID
- Generate an RSA-2048 private key:
# Generate private key (PEM format)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private-key.pemRequirements
- Node.js 20+ (uses native
fetch) - RSA-2048 private key
- Zero runtime dependencies (uses only Node.js built-ins)
Security Notes
- Keep your private key secure and never commit it to version control
- Use environment variables or secure key management
- The SDK uses SHA256withRSA signatures with URL-safe base64 encoding
- All verification happens server-side at tether.name
Publishing
Published to npm automatically via GitHub Actions when a release is created.
Version checklist
Update the version in:
package.json→"version"
Steps
- Update version numbers above
- Commit and push to
main - Create a GitHub release with a matching tag (e.g.
v1.0.0) - CI builds and publishes to npm automatically
Manual publish (if needed)
npm run build
npm publish --access publicLicense
MIT License - see LICENSE file for details.
