A secure OTP Relay Service for AI Agents. Help your AI agents receive verification codes (SMS/email OTPs) securely with end-to-end encryption, user approval, and automatic deletion.
AI agents often need verification codes to complete tasks like signing up for services or logging in on behalf of users. Traditional approaches are risky:
| Approach | Risk |
|---|---|
| Give agent full email access | Agent can read ALL emails (banking, medical, personal) |
| Forward all SMS to agent | Agent can intercept ALL messages (2FA, verification codes) |
| User manually copy-pastes | Breaks automation, causes user fatigue |
Agent OTP provides a secure relay for verification codes:
- End-to-End Encryption: OTPs encrypted with agent's public key - only the agent can decrypt
- User Approval: You control which OTPs your agents can access
- One-Time Read: OTPs auto-deleted after consumption
- Multi-Source Capture: SMS (Android app), Email (Gmail/IMAP)
- Minimal Exposure: Agent only gets specific OTPs from approved senders
┌─────────────────────────────────────────────────────────────────────────┐
│ User's Environment │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Android │ Capture SMS OTP │ Email (Gmail) │ │
│ │ Phone App │ ─────────────┐ │ Email Integration │ │
│ └──────────────┘ │ └──────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ Agent OTP Service │ │
│ │ (Stores encrypted OTP, user approval) │ │
│ └─────────────────┬───────────────────────┘ │
│ │ │
│ │ User approves via Telegram/Web │
│ │ │
└──────────────────────────────────────┼──────────────────────────────────┘
│
│ Encrypted OTP
▼
┌──────────────────┐
│ AI Agent │
│ (Decrypts with │
│ private key) │
└──────────────────┘
The agent generates an RSA key pair. The private key stays with the agent, public key is sent to Agent OTP:
import { generateKeyPair, exportPublicKey } from '@orrisai/agent-otp-sdk';
// Generate key pair (once per agent session)
const { publicKey, privateKey } = await generateKeyPair();
// Private key stored locally, NEVER sent outWhen the agent needs a verification code (e.g., signing up for a service):
const client = new AgentOTPClient({ apiKey: 'ak_xxx' });
const request = await client.requestOTP({
reason: 'Sign up for Acme website', // Tell user why
expectedSender: 'Acme', // Expected sender
filter: {
sources: ['email'], // Only email OTPs
senderPattern: '*@acme.com', // Only from acme.com
},
publicKey: await exportPublicKey(publicKey),
waitForOTP: true,
timeout: 120000,
});User gets notified via Telegram Bot or Dashboard:
🔔 Agent Requests OTP Access
Reason: Sign up for Acme website
Expected Sender: Acme
Source: Email
[✅ Approve] [❌ Deny]
After user approves, Agent OTP monitors for the OTP:
- SMS: Android App listens for SMS matching
senderPattern - Email: Email integration monitors inbox for matching sender
When Acme sends the verification email:
From: noreply@acme.com
Subject: Your verification code
Body: Your code is 847291
Agent OTP service:
- Captures the email
- Extracts code
847291 - Encrypts with agent's public key (only agent can decrypt)
- Stores encrypted data
if (request.status === 'otp_received') {
// Decrypt with private key
const { code } = await client.consumeOTP(request.id, privateKey);
console.log('OTP:', code); // 847291
// Use the code
await completeRegistration(code);
}After consumption, OTP is immediately deleted from server. Cannot be read again.
| Feature | Implementation |
|---|---|
| End-to-End Encryption | OTP encrypted with agent's public key, only private key holder can decrypt |
| Server Cannot Read | Server stores encrypted data, cannot read plaintext codes |
| User Approval | Every OTP request requires explicit user approval |
| One-Time Read | Deleted immediately after consumption, no replay possible |
| Minimal Exposure | Agent only gets specific OTPs from approved senders, not all messages |
Last updated: 2026-01-30
| Component | Status | Description |
|---|---|---|
| TypeScript SDK | ✅ Complete | requestOTP(), consumeOTP(), crypto utilities |
| Shared Package | ✅ Complete | Types, constants, Zod schemas |
| API Service | Route structure exists, some endpoints are placeholders | |
| Documentation Website | ✅ Complete | 35 pages with full documentation |
| Telegram Bot | ✅ Complete | User approval notifications via Grammy |
| Email Integration | ✅ Complete | Gmail API support, OTP extraction |
| Android App (React Native) | ✅ Complete | SMS OTP capture with Expo |
| Web Dashboard | ❌ Not Started | Web-based approval and management |
┌──────────────────────────────────────────────────────────────────────────────┐
│ Agent OTP System │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ AI Agent │──SDK──▶│ API │◀──────▶│ Database │ │
│ │ (requests) │ │ (Hono.js) │ │ (PostgreSQL/Drizzle) │ │
│ └─────────────┘ └──────┬──────┘ └─────────────────────────┘ │
│ │ │
│ ┌───────────────────────┼───────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────────┐ │
│ │ Telegram Bot │ │ Email Service │ │ Android App │ │
│ │ (Grammy) │ │ (Gmail API) │ │ (React Native/Expo) │ │
│ │ │ │ │ │ │ │
│ │ • Approvals │ │ • OTP capture │ │ • SMS OTP capture │ │
│ │ • Denials │ │ • Filtering │ │ • Push notifications │ │
│ │ • Status │ │ • Encryption │ │ • Secure storage │ │
│ └───────────────┘ └───────────────┘ └───────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
- ✅ SDK for AI agents to request and consume OTPs
- ✅ API service for OTP management
- ✅ Telegram bot for user approvals
- ✅ Email integration for email OTP capture
- ✅ Android app for SMS OTP capture
- ❌ Web dashboard for management
- ❌ iOS app (Android only currently)
- ❌ Production deployment (self-hosted only)
npm install @orrisai/agent-otp-sdk
# or
bun add @orrisai/agent-otp-sdkRun a self-hosted instance and create an API key:
docker compose exec api bun run cli agent:create --name "my-assistant"import {
AgentOTPClient,
generateKeyPair,
exportPublicKey,
} from '@orrisai/agent-otp-sdk';
const client = new AgentOTPClient({
apiKey: process.env.AGENT_OTP_API_KEY,
});
// Generate encryption keys
const { publicKey, privateKey } = await generateKeyPair();
// Request an OTP
const request = await client.requestOTP({
reason: 'Sign up verification for Acme Inc',
expectedSender: 'Acme',
filter: {
sources: ['email'],
senderPattern: '*@acme.com',
},
publicKey: await exportPublicKey(publicKey),
waitForOTP: true,
timeout: 120000,
});
// Consume the OTP
if (request.status === 'otp_received') {
const { code } = await client.consumeOTP(request.id, privateKey);
console.log('Received OTP:', code);
}| Status | Description |
|---|---|
pending_approval |
Waiting for user to approve |
approved |
User approved, waiting for OTP to arrive |
otp_received |
OTP captured and ready to consume |
consumed |
OTP has been read and deleted |
denied |
User denied the request |
expired |
Request expired before completion |
cancelled |
Request was cancelled |
agent-otp/
├── apps/
│ ├── api/ # Main API service (Hono + Cloudflare Workers)
│ ├── website/ # Documentation website (Next.js)
│ ├── telegram-bot/ # Telegram approval bot (Grammy)
│ ├── email-integration/# Email OTP capture (Gmail API)
│ ├── mobile/ # React Native SMS app (Expo)
│ └── dashboard/ # Web Dashboard - Coming soon
├── packages/
│ ├── sdk/ # TypeScript SDK
│ └── shared/ # Shared types and utilities
├── docs/ # Internal documentation
└── docker-compose.yml # Local development setup
The following components require manual configuration before use:
- Create a bot with @BotFather to get
TELEGRAM_BOT_TOKEN - Set your Telegram user ID as
TELEGRAM_ADMIN_ID - Configure webhook URL for production deployment
# apps/telegram-bot/.env
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_ADMIN_ID=your_telegram_id
AGENT_OTP_API_URL=http://localhost:8787
AGENT_OTP_API_KEY=your_api_key- Enable Gmail API in Google Cloud Console
- Create OAuth2 credentials (Desktop app type)
- Run the authentication flow to get refresh token
# apps/email-integration/.env
GMAIL_CLIENT_ID=your_client_id
GMAIL_CLIENT_SECRET=your_client_secret
GMAIL_REFRESH_TOKEN=your_refresh_token
AGENT_OTP_API_URL=http://localhost:8787
AGENT_OTP_API_KEY=your_api_key- Install Expo CLI
- Configure API endpoint in app settings
- Build APK or use Expo Go for development
cd apps/mobile
bun install
bun run android # Or use Expo GoNote: SMS permissions require physical Android device (not emulator).
Full documentation is available at agentotp.com/docs.
git clone https://github.com/orristech/agent-otp.git
cd agent-otp
bun install
docker compose up -d
cp .env.example .env
bun devThe API will be available at http://localhost:8787.
bun test # Run all tests
bun test --run # Single run (no watch)
bun test:coverage # With coveragerequestOTP(options: RequestOTPOptions): Promise<OTPRequestResult>
getOTPStatus(requestId: string): Promise<OTPStatus>
consumeOTP(requestId: string, privateKey: CryptoKey): Promise<OTPConsumeResult>
cancelOTPRequest(requestId: string): Promise<void>generateKeyPair(): Promise<CryptoKeyPair>
exportPublicKey(key: CryptoKey): Promise<string>
importPrivateKey(keyData: string): Promise<CryptoKey>
decryptOTPPayload(encrypted: string, privateKey: CryptoKey): Promise<string>Contributions are welcome! Please read our Contributing Guide for details.
MIT License - see LICENSE for details.