Reference

API Reference

Complete reference for all SecureOTP public API endpoints — request formats, response shapes, and status codes.

API Reference

All endpoints are prefixed with your domain. Authentication uses Bearer tokens — include your API key in the Authorization header. Public OTP endpoints accept API keys (sk_test_... / sk_live_...).


OTP — SMS

POST /api/v1/otp/sms/send

Send an OTP via SMS.

Auth: API key

Request body

| Field | Type | Required | Description | | --- | --- | --- | --- | | phone | string | Yes | Recipient phone in E.164 format |

Response 200

json
{
  "message": "OTP sent successfully",
  "expiresAt": "2026-06-09T10:05:00.000Z"
}

Errors: 400 missing phone · 402 insufficient balance · 500 carrier error


POST /api/v1/otp/sms/verify

Verify an SMS OTP.

Auth: API key

Request body

| Field | Type | Required | Description | | --- | --- | --- | --- | | phone | string | Yes | Phone number that received the OTP | | code | string | Yes | Code entered by the user |

Response 200 — success

json
{ "success": true, "message": "OTP verified successfully", "phone": "+628123456789" }

Response 400 — failure

json
{ "success": false, "message": "Invalid or expired OTP" }

OTP — WhatsApp

POST /api/v1/otp/whatsapp/send

Send an OTP via WhatsApp.

Auth: API key

Request body

| Field | Type | Required | Description | | --- | --- | --- | --- | | phone | string | Yes | Recipient phone in E.164 format | | whatsappAccountId | number | No | ID of a specific WhatsApp account (PREMIUM / ADMIN only). Defaults to system default. |

Response 200

json
{
  "message": "OTP sent successfully",
  "expiresAt": "2026-06-09T10:05:00.000Z"
}

Errors: 400 missing phone · 402 insufficient balance · 403 not PREMIUM/ADMIN · 403 account ownership · 404 account not found · 503 no default account configured


POST /api/v1/otp/whatsapp/verify

Verify a WhatsApp OTP.

Auth: API key

Request body

| Field | Type | Required | Description | | --- | --- | --- | --- | | phone | string | Yes | Phone number that received the OTP | | code | string | Yes | Code entered by the user |

Response 200 — success

json
{ "success": true, "message": "OTP verified successfully", "phone": "+628123456789" }

Response 400 — failure

json
{ "success": false, "message": "Invalid or expired OTP" }

API Tokens

GET /api/token

List all API tokens for the authenticated user.

Auth: Console session token

Response 200

json
{
  "tokens": [
    {
      "id": 1,
      "name": "Production",
      "key": "sk_live_••••••••...abcd",
      "environment": "LIVE",
      "active": true,
      "allowedDomains": "example.com",
      "lastUsedAt": "2026-06-09T08:00:00.000Z",
      "createdAt": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Keys are masked in the list response. The full key is only available at creation time.


POST /api/token

Create a new API token.

Auth: Console session token

Request body

| Field | Type | Required | Description | | --- | --- | --- | --- | | name | string | Yes | Human-readable label | | environment | string | No | TEST or LIVE. Default: LIVE | | allowedDomains | string | No | Comma-separated allowed domains |

Response 201 — includes full key (only shown once)

json
{
  "token": {
    "id": 2,
    "name": "Staging",
    "key": "sk_test_abc123def456...",
    "environment": "TEST",
    "active": true,
    "allowedDomains": "",
    "createdAt": "2026-06-09T08:00:00.000Z"
  }
}

PUT /api/token/:id

Revoke (deactivate) a token.

Auth: Console session token (must own the token)

Response 200

json
{ "token": { "id": 1, "name": "Production", "environment": "LIVE", "active": false } }

DELETE /api/token/:id

Permanently delete a token.

Auth: Console session token (must own the token)

Response 204 — no body


Settings

GET /api/settings

Retrieve the authenticated user's notification and webhook settings.

Auth: Console session token

Response 200

json
{
  "data": {
    "webhookUrl": "https://your-server.com/hooks/secureotp",
    "notifyLowCredit": true,
    "notifyWeeklyReport": false,
    "notifyNewLogin": false,
    "notifyLoginActivity": false,
    "twoFactorEnabled": false
  }
}

PUT /api/settings

Update notification and webhook settings.

Auth: Console session token

Request body — all fields optional

| Field | Type | Description | | --- | --- | --- | | webhookUrl | string | HTTPS URL to receive event callbacks. Pass "" or null to clear. | | notifyLowCredit | boolean | Notify on low balance | | notifyWeeklyReport | boolean | Weekly activity report | | notifyNewLogin | boolean | Alert on new login | | notifyLoginActivity | boolean | Log all login events | | twoFactorEnabled | boolean | Enable 2FA |

Response 200 — returns updated settings object


Orders

GET /api/order

List credit top-up orders.

Auth: Console session token

Query parameters

| Parameter | Description | | --- | --- | | page | Page number (default 1) | | limit | Results per page, max 100 (default 20) | | status | Filter by PENDING, COMPLETED, or FAILED | | dateFrom | Start date (ISO 8601) | | dateTo | End date (ISO 8601) |

Response 200

json
{
  "data": [
    {
      "id": 1,
      "invoiceNo": "ORD-20260609-A1B2C",
      "amount": 100000,
      "status": "COMPLETED",
      "paymentVia": "BANK_TRANSFER",
      "paymentDate": "2026-06-09T09:00:00.000Z",
      "createdAt": "2026-06-09T08:00:00.000Z"
    }
  ],
  "lifetimeTotal": 250000,
  "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}

POST /api/order

Create a new top-up order.

Auth: Console session token

Request body

| Field | Type | Required | Description | | --- | --- | --- | --- | | amount | number | Yes | Credit amount to purchase | | paymentVia | string | Yes | BANK_TRANSFER, QRIS, CREDIT_CARD, DEBIT_CARD, E_WALLET, or CASH | | notes | string | No | Optional payment notes |

Response 201

json
{
  "order": {
    "id": 2,
    "invoiceNo": "ORD-20260609-X9YZ1",
    "amount": 50000,
    "status": "PENDING",
    "paymentVia": "QRIS",
    "createdAt": "2026-06-09T10:00:00.000Z"
  }
}

HTTP Status Codes

| Code | Meaning | | --- | --- | | 200 | Success | | 201 | Created | | 204 | No content | | 400 | Bad request — missing or invalid parameters | | 401 | Unauthorized — missing, invalid, or revoked API key | | 402 | Payment required — insufficient credit balance | | 403 | Forbidden — role does not permit this action | | 404 | Not found | | 422 | Unprocessable — validation error | | 500 | Internal server error | | 503 | Service unavailable — WhatsApp account not configured |