11 KiB
UrmiOTP — developer API
Unified OTP API for Urmi developers. Use this document to integrate OTP delivery into your application.
Related docs
| Document | Purpose |
|---|---|
| OTP_SMS_IMPLEMENTATION.md | Direct Ultronsms gateway integration (standalone, not used by UrmiOTP API today) |
| GENERIC_TEMPLATE_SMS.md | Generic DLT template SMS via Ultronsms |
/api/v1/messages/docs/send |
Per-account DLT SMS API (actually delivers SMS today) |
How it is implemented
UrmiOTP is a project-gated, template-based OTP layer on top of the Urmi control-plane API. It is not a standalone SMS gateway integration.
Architecture
┌──────────────────┐ POST /api/v1/otp/generate ┌─────────────────┐
│ Developer app │ ────────────────────────────► │ routes_otp.py │
│ (API key auth) │ └────────┬────────┘
└──────────────────┘ │
▼
┌──────────────────┐ POST /api/v1/urmiotp/generate ┌─────────────────┐
│ Urmi console │ ───────────────────────────────►│ routes_urmiotp │
│ (session auth) │ └────────┬────────┘
└──────────────────┘ │
▼
┌─────────────────┐
│ otp_service.py │
│ generate_otp_ │
│ for_user() │
└────────┬────────┘
│
┌────────────────────────────────────────┼────────────────────────┐
▼ ▼ ▼ ▼
urmiotp_catalog.py UrmiOtpProject normalize_recipients() record_otp_log()
(7 hardcoded (approved project (phone formatting) → otp_message_logs
templates) lookup)
Both entry points call the same core function: generate_otp_for_user() in backend/app/otp_service.py.
Onboarding flow
- Developer creates an UrmiOTP project in the console (
POST /api/v1/urmiotp/projects).- Provides
project_name,message_label(product name shown in SMS),app_type,app_url, andpurpose. - Project starts with
status: pending.
- Provides
- Admin reviews and approves the project (
PATCH /api/v1/admin/compliance/urmiotp-projects/{id}).- Only
approvedprojects can call the generate API.
- Only
- Developer creates an API key (
urmi_live_...) under Develop → API Keys. - Developer calls
POST /api/v1/otp/generatewith the project name, template type, recipient, and OTP variables.
Per-project documentation (templates with your message_label filled in) is available at:
GET /api/v1/urmiotp/projects/{project_id}/documentation
(Requires console session auth; available only after approval.)
Message composition
Templates are defined in backend/app/urmiotp_catalog.py — seven fixed DLT-style message patterns. Each template has:
{message_label}— replaced with the project's registered label (e.g.Acme App){#var#}× 2 — replaced in order withvariables.otpandvariables.validity_minutes
Example for login_authentication with label Acme:
Your URMI login verification code for Acme is 583921. It is valid for 10 minutes. - URMI
Urmi does not generate the OTP code. Your backend must create the numeric code, pass it in variables.otp, store it server-side with a TTL, and verify it when the user submits it.
What happens on generate
When POST /api/v1/otp/generate is called, otp_service.py:
- Validates
template_typeagainst the catalog. - Loads the caller's approved
UrmiOtpProjectbyproject_name. - Normalizes the recipient number (10-digit Indian numbers get
91prefix). - Builds the final SMS text via
_build_otp_message(). - Writes a row to
otp_message_logswithstatus: accepted. - Returns
message_previewandlog_id.
SMS delivery
generate_otp_for_user() sends SMS via UltronsmsClient.send_template_sms() using the DLT-approved template from OTP_SMS_IMPLEMENTATION.md:
Your URMI app verification code is {otp} for {message_label}. It is valid for {validity_minutes} minutes. Do not share it to anyone - URMI AI
| Setting | Value |
|---|---|
| Sender ID | URMIAI |
| PE ID | 1701178780498045011 |
| DLT template ID | 1777178797989033323 |
template_type (e.g. login_authentication) is used for logging and analytics only. All types deliver the same DLT-registered SMS body.
Credentials come from ULTRONSMS_USER and ULTRONSMS_PASSWORD in the server environment.
Observability
OTP requests are stored in otp_message_logs and surfaced in:
- Develop → Logs (
GET /api/v1/logs/otp) - Develop → Analytics (grouped by project and template type)
API reference
Generate OTP
| Method | POST |
| Path | /api/v1/otp/generate |
| Auth | API key (Authorization: Bearer urmi_live_... or X-Api-Key: urmi_live_...) |
| Content-Type | application/json |
Request body
{
"project_name": "acme-login",
"template_type": "login_authentication",
"to": "919999999999",
"variables": {
"otp": "583921",
"validity_minutes": "10"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
project_name |
string | yes | Your approved UrmiOTP project name (3–30 chars) |
template_type |
string | yes | One of the template types below |
to |
string | yes | Recipient mobile with country code (10–15 digits) |
variables.otp |
string | yes | OTP value to embed in the message |
variables.validity_minutes |
string | yes | Validity duration in minutes |
Template types
template_type |
Use case |
|---|---|
general_verification |
Signup, KYC, general verification |
login_authentication |
Login, MFA, 2FA |
account_recovery |
Forgot password, account recovery |
account_change |
Change phone, email, or password |
action_authorization |
Sensitive actions, API key changes |
transaction_verification |
Payments, transfers |
device_verification |
Device pairing, IoT activation |
Success response (200)
{
"success": true,
"log_id": "550e8400-e29b-41d4-a716-446655440000",
"project_name": "acme-login",
"template_type": "login_authentication",
"to": "919999999999",
"message_preview": "Your URMI app verification code is 583921. It is valid for 10 minutes. Do not share it to anyone - URMI AI",
"status": "sent",
"job_id": "215488915",
"message_id": "5h6krUyQsU64iM0otKX7kA"
}
Error responses
| HTTP | Cause |
|---|---|
401 |
Missing or invalid API key |
403 |
Account inactive |
404 |
UrmiOTP project not found for this user |
422 |
Project not approved, unknown template_type, missing variables, or invalid phone number |
Authentication
Create an API key in the Urmi console under Develop → API Keys.
Authorization: Bearer urmi_live_<your_key>
Alternate header:
X-Api-Key: urmi_live_<your_key>
API keys use the urmi_live_ prefix. Implementation: backend/app/api_key_auth.py.
Examples
cURL
curl -X POST "https://urmiai.com/api/v1/otp/generate" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer urmi_live_YOUR_KEY_HERE" \
-d '{
"project_name": "acme-login",
"template_type": "login_authentication",
"to": "919999999999",
"variables": {
"otp": "583921",
"validity_minutes": "10"
}
}'
Local development base URL: http://localhost:8000
Python
import httpx
API_BASE = "https://urmiai.com"
API_KEY = "urmi_live_YOUR_KEY_HERE"
response = httpx.post(
f"{API_BASE}/api/v1/otp/generate",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"project_name": "acme-login",
"template_type": "login_authentication",
"to": "919999999999",
"variables": {"otp": "583921", "validity_minutes": "10"},
},
timeout=30,
)
response.raise_for_status()
print(response.json())
Node.js
const response = await fetch("https://urmiai.com/api/v1/otp/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer urmi_live_YOUR_KEY_HERE",
},
body: JSON.stringify({
project_name: "acme-login",
template_type: "login_authentication",
to: "919999999999",
variables: { otp: "583921", validity_minutes: "10" },
}),
});
const data = await response.json();
Console testing (no API key)
For testing inside the Urmi console or Develop → Testing Center, use the session-authenticated variant:
| Method | POST |
| Path | /api/v1/urmiotp/generate |
| Auth | Console session cookie |
Same request body as /api/v1/otp/generate.
Integration checklist
- Create and get an UrmiOTP project approved
- Create an API key (
urmi_live_...) - Generate OTP codes on your backend (Urmi does not generate them)
- Store OTP server-side with TTL matching
validity_minutes - Verify user-submitted OTP on your backend before marking session verified
- Rate-limit OTP requests per phone number and IP
- Do not return the OTP in your own API responses to the client
- Do not log full OTP values in application logs
- Handle non-
200responses with a generic user message ("Unable to send OTP, try again")
Source files
| File | Role |
|---|---|
backend/app/routes_otp.py |
External API route (/api/v1/otp/generate) |
backend/app/routes_urmiotp.py |
Console routes (projects, docs, session generate) |
backend/app/otp_service.py |
Core generate logic |
backend/app/urmiotp_catalog.py |
Template catalog and API metadata |
backend/app/log_service.py |
record_otp_log() → otp_message_logs |
backend/app/models.py |
UrmiOtpProject, OtpMessageLog models |
backend/app/api_key_auth.py |
API key authentication |
frontend/src/features/agent/UrmiOtpDocsPage.tsx |
In-console API documentation UI |