Files
MySMSAPio/app/views/pages/docs.html.erb
Min Zeya Phyo a5ade4006d
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
welcome page and documentatino
2026-08-01 00:27:37 +08:00

656 lines
46 KiB
Plaintext
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.
<!-- 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>