This commit is contained in:
debu-2 2026-10-07 02:50:28 +05:30
parent e98036cd31
commit 614cf1e7f5
4 changed files with 398 additions and 15 deletions

310
URMIOTP_API.md Normal file
View file

@ -0,0 +1,310 @@
# 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 |