urmi-ai/URMIOTP_API.md
2026-10-07 02:50:28 +05:30

310 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](OTP_SMS_IMPLEMENTATION.md) | Direct Ultronsms gateway integration (standalone, not used by UrmiOTP API today) |
| [GENERIC_TEMPLATE_SMS.md](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
1. **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`, and `purpose`.
- Project starts with `status: pending`.
2. **Admin** reviews and approves the project (`PATCH /api/v1/admin/compliance/urmiotp-projects/{id}`).
- Only `approved` projects can call the generate API.
3. **Developer** creates an API key (`urmi_live_...`) under **Develop → API Keys**.
4. **Developer** calls `POST /api/v1/otp/generate` with 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 with `variables.otp` and `variables.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`:
1. Validates `template_type` against the catalog.
2. Loads the caller's **approved** `UrmiOtpProject` by `project_name`.
3. Normalizes the recipient number (10-digit Indian numbers get `91` prefix).
4. Builds the final SMS text via `_build_otp_message()`.
5. Writes a row to `otp_message_logs` with `status: accepted`.
6. Returns `message_preview` and `log_id`.
### SMS delivery
`generate_otp_for_user()` sends SMS via `UltronsmsClient.send_template_sms()` using the DLT-approved template from [OTP_SMS_IMPLEMENTATION.md](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
```json
{
"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`)
```json
{
"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
```bash
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
```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
```javascript
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-`200` responses 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 |