welcome page and documentatino
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
Min Zeya Phyo
2026-08-01 00:27:37 +08:00
parent 798162ae4f
commit a5ade4006d
4 changed files with 1185 additions and 0 deletions

View File

@@ -0,0 +1,655 @@
<!-- Docs layout: sidebar + content -->
<div class="mx-auto flex max-w-7xl gap-10 px-5 py-12 sm:px-8">
<!-- ============ SIDEBAR ============ -->
<aside class="docs-sidebar hidden w-64 shrink-0 lg:block">
<div class="sticky top-24">
<p class="font-mono-tech px-3 text-[10px] uppercase tracking-[0.25em] text-slate-500">On this page</p>
<nav class="mt-4 flex flex-col gap-0.5 border-l border-white/10 text-sm">
<a href="#getting-started" data-target="getting-started" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Getting Started</a>
<a href="#authentication" data-target="authentication" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Authentication</a>
<a href="#rate-limits" data-target="rate-limits" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Rate Limits</a>
<a href="#errors" data-target="errors" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Error Codes</a>
<p class="font-mono-tech mt-5 px-3 text-[10px] uppercase tracking-[0.25em] text-slate-500">Client API</p>
<a href="#send-sms" data-target="send-sms" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Send SMS</a>
<a href="#sms-status" data-target="sms-status" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Check SMS Status</a>
<a href="#received-sms" data-target="received-sms" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">List Received SMS</a>
<a href="#otp-send" data-target="otp-send" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Send OTP</a>
<a href="#otp-verify" data-target="otp-verify" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Verify OTP</a>
<a href="#admin-stats" data-target="admin-stats" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Admin Stats</a>
<a href="#admin-gateways" data-target="admin-gateways" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Admin Gateways</a>
<p class="font-mono-tech mt-5 px-3 text-[10px] uppercase tracking-[0.25em] text-slate-500">Gateway API</p>
<a href="#gw-register" data-target="gw-register" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Register Gateway</a>
<a href="#gw-heartbeat" data-target="gw-heartbeat" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Heartbeat</a>
<a href="#gw-sms-received" data-target="gw-sms-received" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Report Inbound SMS</a>
<a href="#gw-sms-status" data-target="gw-sms-status" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Report Delivery</a>
<p class="font-mono-tech mt-5 px-3 text-[10px] uppercase tracking-[0.25em] text-slate-500">Realtime</p>
<a href="#websocket" data-target="websocket" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">WebSocket Channel</a>
<a href="#webhooks" data-target="webhooks" class="-ml-px border-l-2 border-transparent px-3 py-1.5 text-slate-400">Webhooks</a>
</nav>
</div>
</aside>
<!-- ============ CONTENT ============ -->
<div class="min-w-0 flex-1 space-y-20">
<!-- Hero -->
<section id="top">
<p class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Reference</p>
<h1 class="font-display mt-3 text-5xl font-extrabold tracking-tight text-white">API Documentation</h1>
<p class="mt-4 max-w-2xl text-lg text-slate-400">Every endpoint, parameter, and response for the MySMSAPio SMS Gateway. All routes are prefixed with <code class="rounded bg-white/5 px-2 py-0.5 font-mono-tech text-sm text-sky-300">/api/v1</code>.</p>
</section>
<!-- ============ GETTING STARTED ============ -->
<section id="getting-started" class="scroll-mt-24">
<h2 class="font-display text-3xl font-bold text-white">Getting Started</h2>
<div class="mt-6 space-y-4 text-slate-300">
<p>The MySMSAPio API is a JSON REST API. There are two classes of consumer:</p>
<ul class="ml-5 list-disc space-y-2 text-slate-400">
<li><span class="font-semibold text-slate-200">Client applications</span> — send SMS, generate/verify OTPs, read inbound messages, and query admin stats. Authenticated with a <code class="font-mono-tech text-sky-300">api_live_…</code> key.</li>
<li><span class="font-semibold text-slate-200">Gateway devices</span> — Android phones that register, send heartbeats, report inbound SMS and delivery receipts. Authenticated with a <code class="font-mono-tech text-sky-300">gw_live_…</code> key.</li>
</ul>
</div>
<div class="mt-6 grid gap-4 sm:grid-cols-2">
<div class="rounded-xl glass p-5">
<p class="font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Base URL</p>
<code class="mt-1 block font-mono-tech text-sm text-sky-300">https://your-host/api/v1</code>
</div>
<div class="rounded-xl glass p-5">
<p class="font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Content-Type</p>
<code class="mt-1 block font-mono-tech text-sm text-sky-300">application/json</code>
</div>
</div>
</section>
<!-- ============ AUTHENTICATION ============ -->
<section id="authentication" class="scroll-mt-24">
<h2 class="font-display text-3xl font-bold text-white">Authentication</h2>
<p class="mt-4 text-slate-400">All authenticated endpoints expect an API key in the <code class="font-mono-tech text-sky-300">Authorization</code> header as a Bearer token. Keys are SHA-256 hashed at rest — the raw key is only shown once at creation.</p>
<div class="mt-6 grid gap-4 sm:grid-cols-2">
<div class="rounded-xl glass p-5">
<div class="flex items-center gap-2">
<i class="fas fa-key text-emerald-400"></i>
<span class="font-semibold text-white">Client key</span>
</div>
<code class="mt-2 block font-mono-tech text-xs text-emerald-300">api_live_&lt;64 hex chars&gt;</code>
<p class="mt-2 text-xs text-slate-400">Permissions-based (send_sms, receive_sms…). Can expire.</p>
</div>
<div class="rounded-xl glass p-5">
<div class="flex items-center gap-2">
<i class="fas fa-mobile-screen text-sky-400"></i>
<span class="font-semibold text-white">Gateway key</span>
</div>
<code class="mt-2 block font-mono-tech text-xs text-sky-300">gw_live_&lt;64 hex chars&gt;</code>
<p class="mt-2 text-xs text-slate-400">Bound to a single gateway device. Must be active.</p>
</div>
</div>
<div class="code-block mt-6 rounded-xl p-4 text-xs leading-relaxed">
<span class="tok-com"># Bearer token in the Authorization header</span><br>
<span class="tok-key">curl</span> https://your-host/api/v1/sms/received \<br>
&nbsp;&nbsp;-H <span class="tok-str">"Authorization: Bearer api_live_..."</span>
</div>
</section>
<!-- ============ RATE LIMITS ============ -->
<section id="rate-limits" class="scroll-mt-24">
<h2 class="font-display text-3xl font-bold text-white">Rate Limits</h2>
<p class="mt-4 text-slate-400">Limits are enforced via Redis and return <code class="font-mono-tech text-sky-300">429 Too Many Requests</code> with a <code class="font-mono-tech text-sky-300">retry_after</code> hint.</p>
<div class="mt-6 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5">
<tr>
<th class="px-5 py-3 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Resource</th>
<th class="px-5 py-3 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Limit</th>
<th class="px-5 py-3 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Window</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
<tr><td class="px-5 py-3 text-slate-300">Send SMS</td><td class="px-5 py-3 text-sky-300">100</td><td class="px-5 py-3 text-slate-400">per minute / API key</td></tr>
<tr><td class="px-5 py-3 text-slate-300">Send OTP</td><td class="px-5 py-3 text-sky-300">3</td><td class="px-5 py-3 text-slate-400">per hour / phone number</td></tr>
<tr><td class="px-5 py-3 text-slate-300">OTP verify attempts</td><td class="px-5 py-3 text-sky-300">3</td><td class="px-5 py-3 text-slate-400">per code</td></tr>
</tbody>
</table>
</div>
</section>
<!-- ============ ERRORS ============ -->
<section id="errors" class="scroll-mt-24">
<h2 class="font-display text-3xl font-bold text-white">Error Codes</h2>
<p class="mt-4 text-slate-400">Errors are returned as JSON with an <code class="font-mono-tech text-sky-300">error</code> field. Validation errors also include <code class="font-mono-tech text-sky-300">details</code>.</p>
<div class="mt-6 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5">
<tr>
<th class="px-5 py-3 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Status</th>
<th class="px-5 py-3 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Meaning</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5">
<% errors = [
["400", "Bad Request — missing required parameter"],
["401", "Unauthorized — missing or invalid API key"],
["403", "Forbidden — insufficient permissions"],
["404", "Not Found — resource does not exist"],
["409", "Conflict — e.g. gateway already registered"],
["422", "Unprocessable Entity — validation failure"],
["429", "Too Many Requests — rate limit exceeded"],
["500", "Internal Server Error"]
] %>
<% errors.each do |code, meaning| %>
<tr>
<td class="px-5 py-3"><span class="rounded-md bg-red-500/10 px-2 py-0.5 font-mono-tech text-xs text-red-300"><%= code %></span></td>
<td class="px-5 py-3 text-slate-400"><%= meaning %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</section>
<!-- ===================== CLIENT API DIVIDER ===================== -->
<div class="flex items-center gap-4">
<span class="h-px flex-1 bg-gradient-to-r from-transparent to-white/15"></span>
<span class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Client Application API</span>
<span class="h-px flex-1 bg-gradient-to-l from-transparent to-white/15"></span>
</div>
<!-- ============ SEND SMS ============ -->
<%# Reusable endpoint block. Data derived from the controller source. %>
<section id="send-sms" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
<code class="font-mono-tech text-lg text-white">/sms/send</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">Send an SMS</h3>
<p class="mt-2 text-slate-400">Queues an outbound message. The API returns immediately with a <code class="font-mono-tech text-sky-300">message_id</code>; delivery happens asynchronously via an available gateway. Rate limited to 100/min per API key.</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
<div class="mt-2 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5"><tr>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Description</th>
</tr></thead>
<tbody class="divide-y divide-white/5">
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">to</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-400">E.164 phone number (validated via Phonelib)</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">message</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-400">Message body (max 1600 chars)</td></tr>
</tbody>
</table>
</div>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Example request</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
<span class="tok-key">curl</span> -X POST https://your-host/api/v1/sms/send \<br>
&nbsp;&nbsp;-H <span class="tok-str">"Authorization: Bearer api_live_..."</span> \<br>
&nbsp;&nbsp;-H <span class="tok-str">"Content-Type: application/json"</span> \<br>
&nbsp;&nbsp;-d <span class="tok-str">'{"to":"+14155551234","message":"Your code is 482915"}'</span>
</div>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 202 Accepted</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{<br>
&nbsp;&nbsp;<span class="tok-key">"success"</span>: <span class="tok-bool">true</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_3f8a9c1b2e7d4f60"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"status"</span>: <span class="tok-str">"queued"</span><br>
}
</div>
</section>
<!-- ============ SMS STATUS ============ -->
<section id="sms-status" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-get">GET</span>
<code class="font-mono-tech text-lg text-white">/sms/status/:message_id</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">Check SMS Status</h3>
<p class="mt-2 text-slate-400">Returns the current lifecycle state of a message, including timestamps.</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Path parameters</p>
<div class="mt-2 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5"><tr>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Description</th>
</tr></thead>
<tbody class="divide-y divide-white/5">
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">message_id</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-slate-400">The ID returned when the message was created</td></tr>
</tbody>
</table>
</div>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{<br>
&nbsp;&nbsp;<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_3f8a9c1b2e7d4f60"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"status"</span>: <span class="tok-str">"delivered"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"sent_at"</span>: <span class="tok-str">"2026-08-01T12:00:00Z"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"delivered_at"</span>: <span class="tok-str">"2026-08-01T12:00:03Z"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"failed_at"</span>: <span class="tok-bool">null</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"error_message"</span>: <span class="tok-bool">null</span><br>
}
</div>
<p class="mt-3 text-xs text-slate-500">Statuses: <code class="font-mono-tech text-sky-300">queued</code> → <code class="font-mono-tech text-sky-300">pending</code> → <code class="font-mono-tech text-sky-300">sent</code> → <code class="font-mono-tech text-sky-300">delivered</code> | <code class="font-mono-tech text-sky-300">failed</code></p>
</section>
<!-- ============ RECEIVED SMS ============ -->
<section id="received-sms" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-get">GET</span>
<code class="font-mono-tech text-lg text-white">/sms/received</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">List Received SMS</h3>
<p class="mt-2 text-slate-400">Paginated list of inbound messages, newest first. Optional filters by phone number or date.</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Query parameters</p>
<div class="mt-2 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5"><tr>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Default</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Description</th>
</tr></thead>
<tbody class="divide-y divide-white/5">
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">phone_number</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-slate-500">—</td><td class="px-5 py-2 text-slate-400">Filter by sender phone</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">since</td><td class="px-5 py-2 text-slate-400">datetime</td><td class="px-5 py-2 text-slate-500">—</td><td class="px-5 py-2 text-slate-400">Only messages after this time</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">limit</td><td class="px-5 py-2 text-slate-400">integer</td><td class="px-5 py-2 text-slate-500">50</td><td class="px-5 py-2 text-slate-400">Items per page</td></tr>
</tbody>
</table>
</div>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{<br>
&nbsp;&nbsp;<span class="tok-key">"messages"</span>: [<br>
&nbsp;&nbsp;&nbsp;&nbsp;{<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_7a2f..."</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="tok-key">"from"</span>: <span class="tok-str">"+14155559999"</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="tok-key">"message"</span>: <span class="tok-str">"STOP"</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="tok-key">"received_at"</span>: <span class="tok-str">"2026-08-01T12:05:00Z"</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;}<br>
&nbsp;&nbsp;],<br>
&nbsp;&nbsp;<span class="tok-key">"total"</span>: <span class="tok-num">1</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"page"</span>: <span class="tok-num">1</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"pages"</span>: <span class="tok-num">1</span><br>
}
</div>
</section>
<!-- ============ OTP SEND ============ -->
<section id="otp-send" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
<code class="font-mono-tech text-lg text-white">/otp/send</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">Send an OTP</h3>
<p class="mt-2 text-slate-400">Generates a 6-digit code, sends it via SMS, and returns the expiry time. Max 3 codes per phone per hour.</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
<div class="mt-2 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5"><tr>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Default</th>
</tr></thead>
<tbody class="divide-y divide-white/5">
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">phone_number</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-500">—</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">purpose</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-slate-500">no</td><td class="px-5 py-2 text-slate-400">authentication</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">expiry_minutes</td><td class="px-5 py-2 text-slate-400">integer</td><td class="px-5 py-2 text-slate-500">no</td><td class="px-5 py-2 text-slate-400">5</td></tr>
</tbody>
</table>
</div>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{<br>
&nbsp;&nbsp;<span class="tok-key">"success"</span>: <span class="tok-bool">true</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"expires_at"</span>: <span class="tok-str">"2026-08-01T12:10:00Z"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_9b1c..."</span><br>
}
</div>
</section>
<!-- ============ OTP VERIFY ============ -->
<section id="otp-verify" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
<code class="font-mono-tech text-lg text-white">/otp/verify</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">Verify an OTP</h3>
<p class="mt-2 text-slate-400">Validates the 6-digit code. After 3 failed attempts the code is expired. On success the code is marked verified and cannot be reused.</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
<div class="mt-2 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5"><tr>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
</tr></thead>
<tbody class="divide-y divide-white/5">
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">phone_number</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">code</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td></tr>
</tbody>
</table>
</div>
<div class="mt-5 grid gap-4 sm:grid-cols-2">
<div>
<p class="font-mono-tech text-[10px] uppercase tracking-wider text-emerald-400">Success · 200</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{<br>
&nbsp;&nbsp;<span class="tok-key">"success"</span>: <span class="tok-bool">true</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"verified"</span>: <span class="tok-bool">true</span><br>
}
</div>
</div>
<div>
<p class="font-mono-tech text-[10px] uppercase tracking-wider text-red-400">Failure · 200</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{<br>
&nbsp;&nbsp;<span class="tok-key">"success"</span>: <span class="tok-bool">false</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"verified"</span>: <span class="tok-bool">false</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"error"</span>: <span class="tok-str">"Invalid or expired OTP"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"attempts_remaining"</span>: <span class="tok-num">2</span><br>
}
</div>
</div>
</div>
</section>
<!-- ============ ADMIN STATS ============ -->
<section id="admin-stats" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-get">GET</span>
<code class="font-mono-tech text-lg text-white">/admin/stats</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">Admin Statistics</h3>
<p class="mt-2 text-slate-400">Aggregate system metrics: gateway counts, message throughput, pending/failed counts, and OTP performance for today.</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{<br>
&nbsp;&nbsp;<span class="tok-key">"gateways"</span>: { <span class="tok-key">"total"</span>: <span class="tok-num">4</span>, <span class="tok-key">"active"</span>: <span class="tok-num">3</span>, <span class="tok-key">"online"</span>: <span class="tok-num">2</span>, <span class="tok-key">"offline"</span>: <span class="tok-num">2</span> },<br>
&nbsp;&nbsp;<span class="tok-key">"messages"</span>: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="tok-key">"total_sent"</span>: <span class="tok-num">10234</span>, <span class="tok-key">"total_received"</span>: <span class="tok-num">8821</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="tok-key">"sent_today"</span>: <span class="tok-num">142</span>, <span class="tok-key">"received_today"</span>: <span class="tok-num">98</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="tok-key">"total_today"</span>: <span class="tok-num">240</span>, <span class="tok-key">"pending"</span>: <span class="tok-num">3</span>, <span class="tok-key">"failed_today"</span>: <span class="tok-num">1</span><br>
&nbsp;&nbsp;},<br>
&nbsp;&nbsp;<span class="tok-key">"otp"</span>: { <span class="tok-key">"sent_today"</span>: <span class="tok-num">56</span>, <span class="tok-key">"verified_today"</span>: <span class="tok-num">49</span>, <span class="tok-key">"verification_rate"</span>: <span class="tok-num">87.5</span> },<br>
&nbsp;&nbsp;<span class="tok-key">"timestamp"</span>: <span class="tok-str">"2026-08-01T12:00:00Z"</span><br>
}
</div>
</section>
<!-- ============ ADMIN GATEWAYS ============ -->
<section id="admin-gateways" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-get">GET</span>
<code class="font-mono-tech text-lg text-white">/admin/gateways</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">List Gateways</h3>
<p class="mt-2 text-slate-400">Returns every registered gateway device with live status and counters.</p>
<div class="mt-5 flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
<code class="font-mono-tech text-lg text-white">/admin/gateways/:id/toggle</code>
</div>
<p class="mt-2 text-slate-400">Activate or deactivate a gateway. Toggling off stops it from receiving outbound commands.</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Toggle response · 200 OK</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{<br>
&nbsp;&nbsp;<span class="tok-key">"success"</span>: <span class="tok-bool">true</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"gateway"</span>: { <span class="tok-key">"id"</span>: <span class="tok-num">1</span>, <span class="tok-key">"device_id"</span>: <span class="tok-str">"pixel-001"</span>, <span class="tok-key">"active"</span>: <span class="tok-bool">false</span> }<br>
}
</div>
</section>
<!-- ===================== GATEWAY API DIVIDER ===================== -->
<div class="flex items-center gap-4">
<span class="h-px flex-1 bg-gradient-to-r from-transparent to-white/15"></span>
<span class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Gateway Device API</span>
<span class="h-px flex-1 bg-gradient-to-l from-transparent to-white/15"></span>
</div>
<!-- ============ GW REGISTER ============ -->
<section id="gw-register" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
<code class="font-mono-tech text-lg text-white">/gateway/register</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">Register a Gateway</h3>
<p class="mt-2 text-slate-400"><span class="rounded-md bg-emerald-500/10 px-2 py-0.5 font-mono-tech text-[10px] uppercase text-emerald-300">No auth</span> &nbsp; Registers an Android device and returns a fresh gateway API key. Returns <code class="font-mono-tech text-sky-300">409</code> if the device_id already exists.</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
<div class="mt-2 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5"><tr>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Description</th>
</tr></thead>
<tbody class="divide-y divide-white/5">
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">device_id</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-400">Unique device identifier</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">name</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-slate-500">no</td><td class="px-5 py-2 text-slate-400">Friendly name (defaults to device id prefix)</td></tr>
</tbody>
</table>
</div>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 201 Created</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{<br>
&nbsp;&nbsp;<span class="tok-key">"success"</span>: <span class="tok-bool">true</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"api_key"</span>: <span class="tok-str">"gw_live_a1b2c3..."</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"device_id"</span>: <span class="tok-str">"pixel-001"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"websocket_url"</span>: <span class="tok-str">"wss://your-host/cable"</span><br>
}
</div>
<p class="mt-3 rounded-lg border border-amber-400/20 bg-amber-500/5 px-4 py-2 text-xs text-amber-200/80"><i class="fas fa-triangle-exclamation"></i> Save <code class="font-mono-tech">api_key</code> immediately — it is never shown again.</p>
</section>
<!-- ============ GW HEARTBEAT ============ -->
<section id="gw-heartbeat" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
<code class="font-mono-tech text-lg text-white">/gateway/heartbeat</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">Gateway Heartbeat</h3>
<p class="mt-2 text-slate-400">Refreshes the gateway's online status. A gateway is marked offline if no heartbeat arrives within 2 minutes (handled by <code class="font-mono-tech text-sky-300">CheckGatewayHealthJob</code>).</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Optional body</p>
<div class="mt-2 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5"><tr>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Description</th>
</tr></thead>
<tbody class="divide-y divide-white/5">
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">battery_level</td><td class="px-5 py-2 text-slate-400">integer</td><td class="px-5 py-2 text-slate-400">0100</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">signal_strength</td><td class="px-5 py-2 text-slate-400">integer</td><td class="px-5 py-2 text-slate-400">dBm</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">messages_in_queue</td><td class="px-5 py-2 text-slate-400">integer</td><td class="px-5 py-2 text-slate-400">Pending outbound count on device</td></tr>
</tbody>
</table>
</div>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{ <span class="tok-key">"success"</span>: <span class="tok-bool">true</span>, <span class="tok-key">"pending_messages"</span>: <span class="tok-num">3</span> }
</div>
</section>
<!-- ============ GW SMS RECEIVED ============ -->
<section id="gw-sms-received" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
<code class="font-mono-tech text-lg text-white">/gateway/sms/received</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">Report Inbound SMS</h3>
<p class="mt-2 text-slate-400">Called by the gateway when a new SMS arrives on the device. Creates an inbound record and triggers <code class="font-mono-tech text-sky-300">ProcessInboundSmsJob</code> (which fires webhooks).</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
<div class="mt-2 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5"><tr>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
</tr></thead>
<tbody class="divide-y divide-white/5">
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">sender</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">message</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">timestamp</td><td class="px-5 py-2 text-slate-400">datetime</td><td class="px-5 py-2 text-slate-500">no</td></tr>
</tbody>
</table>
</div>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{ <span class="tok-key">"success"</span>: <span class="tok-bool">true</span>, <span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_7a2f..."</span> }
</div>
</section>
<!-- ============ GW SMS STATUS ============ -->
<section id="gw-sms-status" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">POST</span>
<code class="font-mono-tech text-lg text-white">/gateway/sms/status</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">Report Delivery Status</h3>
<p class="mt-2 text-slate-400">Updates an outbound message's status. On <code class="font-mono-tech text-sky-300">failed</code>, <code class="font-mono-tech text-sky-300">RetryFailedSmsJob</code> is enqueued if retries remain (&lt; 3).</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Request body</p>
<div class="mt-2 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5"><tr>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Parameter</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Type</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Required</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Values</th>
</tr></thead>
<tbody class="divide-y divide-white/5">
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">message_id</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-500">—</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">status</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-red-300">yes</td><td class="px-5 py-2 text-slate-400">sent · delivered · failed</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">error_message</td><td class="px-5 py-2 text-slate-400">string</td><td class="px-5 py-2 text-slate-500">no</td><td class="px-5 py-2 text-slate-400">Reason for failure</td></tr>
</tbody>
</table>
</div>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Response · 200 OK</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{ <span class="tok-key">"success"</span>: <span class="tok-bool">true</span> }
</div>
</section>
<!-- ===================== REALTIME DIVIDER ===================== -->
<div class="flex items-center gap-4">
<span class="h-px flex-1 bg-gradient-to-r from-transparent to-white/15"></span>
<span class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Realtime</span>
<span class="h-px flex-1 bg-gradient-to-l from-transparent to-white/15"></span>
</div>
<!-- ============ WEBSOCKET ============ -->
<section id="websocket" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-ws">WS</span>
<code class="font-mono-tech text-lg text-white">/cable</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">WebSocket Channel</h3>
<p class="mt-2 text-slate-400">Gateways maintain a persistent Action Cable connection for real-time command dispatch and status reporting. Connections authenticate with the gateway API key as a query param.</p>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Connect</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
<span class="tok-key">wscat</span> -c <span class="tok-str">"wss://your-host/cable?api_key=gw_live_..."</span>
</div>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Server → Gateway commands</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{<br>
&nbsp;&nbsp;<span class="tok-key">"action"</span>: <span class="tok-str">"send_sms"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_3f8a..."</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"recipient"</span>: <span class="tok-str">"+14155551234"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"message"</span>: <span class="tok-str">"Hello!"</span><br>
}
</div>
<p class="mt-5 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Gateway → Server events</p>
<div class="mt-2 overflow-hidden rounded-xl glass">
<table class="min-w-full divide-y divide-white/10 text-sm">
<thead class="bg-white/5"><tr>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">action</th>
<th class="px-5 py-2 text-left font-mono-tech text-[10px] uppercase tracking-wider text-slate-400">Payload</th>
</tr></thead>
<tbody class="divide-y divide-white/5">
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">heartbeat</td><td class="px-5 py-2 text-slate-400">battery_level, signal_strength, messages_in_queue</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">delivery_report</td><td class="px-5 py-2 text-slate-400">message_id, status, error_message</td></tr>
<tr><td class="px-5 py-2 font-mono-tech text-sky-300">message_received</td><td class="px-5 py-2 text-slate-400">sender, message, timestamp</td></tr>
</tbody>
</table>
</div>
</section>
<!-- ============ WEBHOOKS ============ -->
<section id="webhooks" class="scroll-mt-24">
<div class="flex flex-wrap items-center gap-3">
<span class="rounded-md border px-2.5 py-1 font-mono-tech text-xs font-semibold badge-post">WEBHOOK</span>
<code class="font-mono-tech text-lg text-white">Outbound HTTP POST</code>
</div>
<h3 class="font-display mt-3 text-2xl font-bold text-white">Webhooks</h3>
<p class="mt-2 text-slate-400">When configured, the API delivers signed event payloads to your URL via <code class="font-mono-tech text-sky-300">TriggerWebhookJob</code>. If a secret key is set, each request is signed with HMAC-SHA256.</p>
<div class="mt-6 grid gap-4 sm:grid-cols-3">
<% events = [
{ event: "sms_received", icon: "fa-arrow-down", color: "text-emerald-400", desc: "Inbound SMS arrived" },
{ event: "sms_sent", icon: "fa-paper-plane", color: "text-sky-400", desc: "Message dispatched to gateway" },
{ event: "sms_failed", icon: "fa-circle-xmark", color: "text-red-400", desc: "Delivery failed" }
] %>
<% events.each do |e| %>
<div class="rounded-xl glass p-5">
<i class="fas <%= e[:icon] %> <%= e[:color] %>"></i>
<code class="mt-2 block font-mono-tech text-sm text-white"><%= e[:event] %></code>
<p class="mt-1 text-xs text-slate-400"><%= e[:desc] %></p>
</div>
<% end %>
</div>
<p class="mt-6 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Example payload (sms_received)</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
{<br>
&nbsp;&nbsp;<span class="tok-key">"event"</span>: <span class="tok-str">"sms_received"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_7a2f..."</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"from"</span>: <span class="tok-str">"+14155559999"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"message"</span>: <span class="tok-str">"Confirm"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"received_at"</span>: <span class="tok-str">"2026-08-01T12:05:00Z"</span><br>
}
</div>
<p class="mt-6 font-mono-tech text-[10px] uppercase tracking-wider text-slate-500">Verifying the signature</p>
<div class="code-block mt-2 rounded-xl p-4 text-xs leading-relaxed">
<span class="tok-com"># Ruby</span><br>
signature = OpenSSL::HMAC.hexdigest(<span class="tok-str">"SHA256"</span>, secret_key, payload.to_json)<br>
<span class="tok-com"># Compare with the X-Webhook-Signature header</span>
</div>
</section>
<!-- Back to top -->
<div class="pt-10 text-center">
<a href="#top" class="btn-ghost inline-flex items-center gap-2 rounded-xl px-5 py-2.5 text-sm font-semibold text-slate-200">
<i class="fas fa-arrow-up"></i> Back to top
</a>
</div>
</div>
</div>

View File

@@ -0,0 +1,242 @@
<!-- ===================== HERO ===================== -->
<section class="relative px-5 pt-20 pb-24 sm:px-8">
<div class="mx-auto max-w-5xl text-center">
<!-- Badge -->
<div class="fade-up inline-flex items-center gap-2 rounded-full glass px-4 py-1.5 text-xs">
<span class="relative flex h-2 w-2">
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-sky-400 opacity-75"></span>
<span class="relative inline-flex h-2 w-2 rounded-full bg-sky-400"></span>
</span>
<span class="font-mono-tech uppercase tracking-[0.25em] text-slate-300">SMS Gateway · API v1</span>
</div>
<!-- Beacon logo -->
<div class="fade-up d-1 relative mx-auto mt-10 h-24 w-24 flex items-center justify-center">
<span class="beacon-ring absolute inset-0 rounded-full border border-sky-400/40"></span>
<span class="beacon-ring delay-1 absolute inset-0 rounded-full border border-sky-400/30"></span>
<span class="beacon-ring delay-2 absolute inset-0 rounded-full border border-sky-400/20"></span>
<div class="relative flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-sky-400 to-sky-600 shadow-xl shadow-sky-500/40">
<i class="fas fa-sms text-3xl text-slate-950"></i>
</div>
</div>
<h1 class="fade-up d-2 font-display mt-8 text-5xl font-extrabold tracking-tight text-white sm:text-7xl">
Send SMS through your <span class="gradient-text">Android devices</span>
</h1>
<p class="fade-up d-3 mx-auto mt-6 max-w-2xl text-lg text-slate-400 sm:text-xl">
A production-grade REST &amp; WebSocket API that turns any Android phone into a
programmable SMS gateway. Send messages, generate OTPs, receive inbound texts,
and orchestrate fleets of devices in real time.
</p>
<div class="fade-up d-4 mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
<%= link_to docs_path, class: "btn-accent inline-flex items-center gap-2 rounded-xl px-7 py-3.5 text-base font-semibold text-slate-950" do %>
<i class="fas fa-book-open"></i> Explore the API
<% end %>
<%= link_to admin_login_path, class: "btn-ghost inline-flex items-center gap-2 rounded-xl px-7 py-3.5 text-base font-semibold text-slate-200" do %>
<i class="fas fa-gauge-high"></i> Admin Console
<% end %>
</div>
<!-- Base URL chip -->
<div class="fade-up d-5 mx-auto mt-12 inline-flex items-center gap-3 rounded-xl glass px-5 py-3">
<span class="font-mono-tech text-[11px] uppercase tracking-wider text-slate-500">Base URL</span>
<code class="font-mono-tech text-sm text-sky-300">https://&lt;your-host&gt;/api/v1</code>
<button data-copy="https://your-host/api/v1" class="copy-btn text-slate-500 hover:text-sky-400" aria-label="Copy base URL">
<i class="far fa-copy"></i>
</button>
</div>
</div>
</section>
<!-- ===================== STAT STRIP ===================== -->
<section class="relative px-5 sm:px-8">
<div class="mx-auto max-w-5xl">
<div class="grid grid-cols-2 gap-4 rounded-2xl glass px-6 py-6 sm:grid-cols-4">
<% stats = [
{ icon: "fa-bolt", value: "< 200ms", label: "API latency" },
{ icon: "fa-infinity", value: "Unlimited", label: "Gateways" },
{ icon: "fa-shield-halved", value: "SHA-256", label: "Key hashing" },
{ icon: "fa-rotate", value: "3× retry", label: "Auto backoff" }
] %>
<% stats.each do |s| %>
<div class="flex flex-col items-center text-center">
<i class="fas <%= s[:icon] %> text-sky-400"></i>
<span class="mt-2 font-display text-xl font-bold text-white"><%= s[:value] %></span>
<span class="font-mono-tech text-[10px] uppercase tracking-[0.2em] text-slate-500"><%= s[:label] %></span>
</div>
<% end %>
</div>
</div>
</section>
<!-- ===================== FEATURES ===================== -->
<section class="relative px-5 py-24 sm:px-8">
<div class="mx-auto max-w-6xl">
<div class="mx-auto max-w-2xl text-center">
<p class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Capabilities</p>
<h2 class="font-display mt-3 text-4xl font-bold text-white sm:text-5xl">Everything you need to route SMS at scale</h2>
<p class="mt-4 text-slate-400">From a single device to a distributed fleet — message queuing, delivery tracking, and OTP workflows are built in.</p>
</div>
<div class="mt-14 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
<% features = [
{ icon: "fa-paper-plane", color: "from-sky-400 to-sky-600", title: "Send SMS", desc: "Queue outbound messages and let the gateway fleet deliver them. Automatic retries with exponential backoff." },
{ icon: "fa-arrow-down", color: "from-emerald-400 to-emerald-600", title: "Receive SMS", desc: "Inbound messages land in your inbox instantly and fire webhooks so your app reacts in real time." },
{ icon: "fa-shield-halved", color: "from-violet-400 to-violet-600", title: "OTP Generation", desc: "Six-digit codes with 5-minute expiry, 3-attempt limits, and 3-per-hour rate caps per phone number." },
{ icon: "fa-plug", color: "from-amber-400 to-orange-600", title: "Webhooks", desc: "HMAC-signed event payloads for sms_received, sms_sent and sms_failed with configurable retries." },
{ icon: "fa-tower-broadcast", color: "from-pink-400 to-rose-600", title: "Realtime WebSocket", desc: "Bidirectional Action Cable channel keeps every gateway in sync — heartbeats, delivery reports, and commands." },
{ icon: "fa-mobile-screen", color: "from-blue-400 to-indigo-600", title: "Gateway Fleet", desc: "Register unlimited Android devices. Priority-based routing, health checks, and live status monitoring." }
] %>
<% features.each_with_index do |f, i| %>
<div class="fade-up d-<%= (i % 3) + 1 %> group rounded-2xl glass p-6 transition-all duration-300 hover:-translate-y-1 hover:border-sky-400/30">
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br <%= f[:color] %> shadow-lg">
<i class="fas <%= f[:icon] %> text-lg text-slate-950"></i>
</div>
<h3 class="font-display mt-5 text-xl font-bold text-white"><%= f[:title] %></h3>
<p class="mt-2 text-sm leading-relaxed text-slate-400"><%= f[:desc] %></p>
</div>
<% end %>
</div>
</div>
</section>
<!-- ===================== ARCHITECTURE ===================== -->
<section class="relative px-5 py-24 sm:px-8">
<div class="mx-auto max-w-6xl">
<div class="mx-auto max-w-2xl text-center">
<p class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Architecture</p>
<h2 class="font-display mt-3 text-4xl font-bold text-white sm:text-5xl">How messages flow</h2>
<p class="mt-4 text-slate-400">A clean, asynchronous pipeline keeps the API fast and delivery resilient.</p>
</div>
<div class="mt-14 grid items-stretch gap-4 lg:grid-cols-5">
<% steps = [
{ n: "01", icon: "fa-code", title: "Client calls API", desc: "POST /api/v1/sms/send with a Bearer API key. Phone numbers are validated with Phonelib." },
{ n: "02", icon: "fa-database", title: "Message queued", desc: "SmsMessage is persisted and SendSmsJob is enqueued via Sidekiq. The API returns instantly with a message_id." },
{ n: "03", icon: "fa-route", title: "Gateway selected", desc: "SendSmsJob picks the highest-priority online, active gateway and broadcasts over Action Cable." },
{ n: "04", icon: "fa-mobile-screen-button", title: "Android delivers", desc: "The gateway device sends the SMS via its native radio and reports back a delivery status." },
{ n: "05", icon: "fa-circle-check", title: "Status & webhooks", desc: "Delivery/failed status is recorded; webhooks fire for subscribed events. Failed messages auto-retry up to 3×." }
] %>
<% steps.each do |s| %>
<div class="relative rounded-2xl glass p-6">
<span class="font-mono-tech text-xs text-sky-400/70"><%= s[:n] %></span>
<i class="fas <%= s[:icon] %> mt-3 text-2xl text-sky-400"></i>
<h3 class="font-display mt-3 text-base font-bold text-white"><%= s[:title] %></h3>
<p class="mt-2 text-xs leading-relaxed text-slate-400"><%= s[:desc] %></p>
</div>
<% end %>
</div>
</div>
</section>
<!-- ===================== QUICK START ===================== -->
<section class="relative px-5 py-24 sm:px-8">
<div class="mx-auto max-w-5xl">
<div class="text-center">
<p class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Quick Start</p>
<h2 class="font-display mt-3 text-4xl font-bold text-white sm:text-5xl">Send your first message</h2>
</div>
<div class="mt-12 grid gap-6 lg:grid-cols-2">
<!-- Step 1 -->
<div class="rounded-2xl glass p-6">
<div class="flex items-center gap-3">
<span class="flex h-7 w-7 items-center justify-center rounded-full bg-sky-500 font-mono-tech text-xs font-bold text-slate-950">1</span>
<h3 class="font-display text-lg font-bold text-white">Register a gateway device</h3>
</div>
<p class="mt-3 text-sm text-slate-400">No auth required — receive a gateway API key back.</p>
<div class="code-block mt-4 rounded-xl p-4 text-xs leading-relaxed">
<span class="tok-com"># Register your Android device</span><br>
<span class="tok-key">curl</span> -X POST https://your-host/api/v1/gateway/register \<br>
&nbsp;&nbsp;-H <span class="tok-str">"Content-Type: application/json"</span> \<br>
&nbsp;&nbsp;-d <span class="tok-str">'{"device_id":"pixel-001","name":"Office Phone"}'</span>
</div>
<div class="mt-3 flex items-center gap-2">
<button data-copy='curl -X POST https://your-host/api/v1/gateway/register -H "Content-Type: application/json" -d "{\"device_id\":\"pixel-001\",\"name\":\"Office Phone\"}"' class="copy-btn text-xs text-slate-500 hover:text-sky-400"><i class="far fa-copy"></i> Copy</button>
</div>
</div>
<!-- Step 2 -->
<div class="rounded-2xl glass p-6">
<div class="flex items-center gap-3">
<span class="flex h-7 w-7 items-center justify-center rounded-full bg-sky-500 font-mono-tech text-xs font-bold text-slate-950">2</span>
<h3 class="font-display text-lg font-bold text-white">Connect via WebSocket</h3>
</div>
<p class="mt-3 text-sm text-slate-400">Keep the device online and ready to receive commands.</p>
<div class="code-block mt-4 rounded-xl p-4 text-xs leading-relaxed">
<span class="tok-com"># wscat</span><br>
<span class="tok-key">wscat</span> -c <span class="tok-str">"ws://your-host:3000/cable?api_key=gw_live_..."</span>
</div>
</div>
<!-- Step 3 -->
<div class="rounded-2xl glass p-6 lg:col-span-2">
<div class="flex items-center gap-3">
<span class="flex h-7 w-7 items-center justify-center rounded-full bg-sky-500 font-mono-tech text-xs font-bold text-slate-950">3</span>
<h3 class="font-display text-lg font-bold text-white">Send an SMS</h3>
</div>
<p class="mt-3 text-sm text-slate-400">Use a client API key (api_live_…) in the Authorization header.</p>
<div class="code-block mt-4 rounded-xl p-4 text-xs leading-relaxed">
<span class="tok-key">curl</span> -X POST https://your-host/api/v1/sms/send \<br>
&nbsp;&nbsp;-H <span class="tok-str">"Authorization: Bearer api_live_..."</span> \<br>
&nbsp;&nbsp;-H <span class="tok-str">"Content-Type: application/json"</span> \<br>
&nbsp;&nbsp;-d <span class="tok-str">'{"to":"+14155551234","message":"Hello from MySMSAPio!"}'</span>
</div>
<div class="mt-3 flex items-center gap-2">
<button data-copy='curl -X POST https://your-host/api/v1/sms/send -H "Authorization: Bearer api_live_..." -H "Content-Type: application/json" -d "{\"to\":\"+14155551234\",\"message\":\"Hello from MySMSAPio!\"}"' class="copy-btn text-xs text-slate-500 hover:text-sky-400"><i class="far fa-copy"></i> Copy</button>
</div>
<div class="code-block mt-4 rounded-xl p-4 text-xs leading-relaxed">
<span class="tok-com"># → 202 Accepted</span><br>
{<br>
&nbsp;&nbsp;<span class="tok-key">"success"</span>: <span class="tok-bool">true</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"message_id"</span>: <span class="tok-str">"msg_3f8a9c1b2e7d4f60"</span>,<br>
&nbsp;&nbsp;<span class="tok-key">"status"</span>: <span class="tok-str">"queued"</span><br>
}
</div>
</div>
</div>
<div class="mt-10 text-center">
<%= link_to docs_path, class: "btn-ghost inline-flex items-center gap-2 rounded-xl px-6 py-3 text-sm font-semibold text-slate-200" do %>
<i class="fas fa-arrow-right-long"></i> Read the full API reference
<% end %>
</div>
</div>
</section>
<!-- ===================== TECH STACK ===================== -->
<section class="relative px-5 py-24 sm:px-8">
<div class="mx-auto max-w-5xl">
<div class="text-center">
<p class="font-mono-tech text-[11px] uppercase tracking-[0.3em] text-sky-400/80">Built on a modern stack</p>
<h2 class="font-display mt-3 text-4xl font-bold text-white sm:text-5xl">Production-ready by default</h2>
</div>
<div class="mt-12 flex flex-wrap items-center justify-center gap-3">
<% stack = ["Rails 8.0", "Ruby 3.4", "PostgreSQL", "Redis 7", "Sidekiq 7", "Action Cable", "Puma + Thruster", "Docker / Kamal", "Tailwind CSS", "Phonelib", "HTTParty", "ROTP"] %>
<% stack.each do |tech| %>
<span class="rounded-full glass px-5 py-2 font-mono-tech text-xs text-slate-300"><%= tech %></span>
<% end %>
</div>
</div>
</section>
<!-- ===================== CTA ===================== -->
<section class="relative px-5 py-24 sm:px-8">
<div class="mx-auto max-w-4xl">
<div class="relative overflow-hidden rounded-3xl glass-strong px-8 py-16 text-center sm:px-16">
<div class="pointer-events-none absolute -top-24 left-1/2 h-64 w-64 -translate-x-1/2 rounded-full bg-sky-500/20 blur-3xl"></div>
<h2 class="font-display relative text-4xl font-bold text-white sm:text-5xl">Ready to send your first SMS?</h2>
<p class="relative mx-auto mt-4 max-w-xl text-slate-400">Dive into the complete API reference or jump straight into the admin console to manage your gateways.</p>
<div class="relative mt-8 flex flex-col items-center justify-center gap-4 sm:flex-row">
<%= link_to docs_path, class: "btn-accent inline-flex items-center gap-2 rounded-xl px-7 py-3.5 text-base font-semibold text-slate-950" do %>
<i class="fas fa-book"></i> API Documentation
<% end %>
<%= link_to admin_login_path, class: "btn-ghost inline-flex items-center gap-2 rounded-xl px-7 py-3.5 text-base font-semibold text-slate-200" do %>
<i class="fas fa-gauge-high"></i> Open Admin Console
<% end %>
</div>
</div>
</div>
</section>