export const otpOnlyCode = `// Step 1: send the OTP
await fetch('https://api.minimoth.dev/v1/otp/send', {
  method: 'POST',
  headers: { 'X-Api-Key': 'mm_live_...', 'Content-Type': 'application/json' },
  body: JSON.stringify({ phone: '+919876543210' }),
})

// Step 2: verify the code
const res = await fetch('https://api.minimoth.dev/v1/otp/verify', {
  method: 'POST',
  headers: { 'X-Api-Key': 'mm_live_...', 'Content-Type': 'application/json' },
  body: JSON.stringify({ phone: '+919876543210', code: '123456' }),
})

if (!res.ok) {
  // wrong code, too many attempts, or OTP expired
  throw new Error('OTP verification failed')
}

const { access_token } = await res.json()
// access_token proves the phone was verified — now use your own auth:
// - create a row in your sessions table
// - set a cookie
// MiniMoth's access_token and refresh_token are optional — ignore them if you prefer`

export const protectRouteCode = `// Middleware / route guard (Node.js / Express example)
// Verifies the access_token locally as a JWT — no network round trip per
// request. Fetch and cache MiniMoth's public signing keys once, refetching
// on a 'kid' cache miss rather than on every request.
const jwt = require('jsonwebtoken')
const { createPublicKey } = require('crypto')

let jwks = null
async function getPublicKey(kid) {
  if (!jwks?.keys.some(k => k.kid === kid)) {
    const res = await fetch('https://api.minimoth.dev/.well-known/jwks.json')
    jwks = await res.json()
  }
  const jwk = jwks.keys.find(k => k.kid === kid)
  return createPublicKey({ key: jwk, format: 'jwk' })
}

async function requireAuth(req, res, next) {
  const token = req.headers['x-access-token'] ?? req.cookies.access_token
  if (!token) return res.status(401).json({ error: 'No token' })

  try {
    const { kid } = jwt.decode(token, { complete: true }).header
    const publicKey = await getPublicKey(kid)
    const payload = jwt.verify(token, publicKey, { algorithms: ['RS256'] })
    req.sessionId = payload.session_id
    next()
  } catch {
    res.status(401).json({ error: 'Invalid or expired session' })
  }
}`

export const refreshLoopCode = `// Client-side fetch helper (works with any framework)
// Store access_token in memory, refresh_token in an httpOnly cookie (set by your server)

let accessToken = null

async function apiFetch(url, options = {}) {
  const res = await fetch(url, {
    ...options,
    headers: { ...options.headers, 'Authorization': \`Bearer \${accessToken}\` },
  })

  // Server auto-refreshed the token (SDK detected expiry in middleware) — update local copy.
  // Must happen on every response so the next request uses the new token, not the old one.
  const newToken = res.headers.get('X-New-Access-Token')
  if (newToken) accessToken = newToken

  // 401 means the access token is expired and the server could not auto-refresh
  // (e.g. no refresh token in store). Fall back to calling the refresh endpoint directly.
  if (res.status === 401) {
    const refreshed = await fetch('/auth/refresh', { method: 'POST' })
    if (!refreshed.ok) {
      window.location.href = '/login'
      return
    }
    const { access_token } = await refreshed.json()
    accessToken = access_token

    return fetch(url, {
      ...options,
      headers: { ...options.headers, 'Authorization': \`Bearer \${accessToken}\` },
    })
  }

  return res
}

// Your server's /auth/refresh endpoint calls MiniMoth:
// POST /v1/session/refresh with the refresh_token from the httpOnly cookie
// Returns new access_token + new refresh_token (set new cookie, return new access_token)`

export const sdkExpressCode = `import express from 'express'
import cookieParser from 'cookie-parser'
import { MiniMoth, MiniMothError } from '@minimoth/sdk-node'

const app = express()
app.use(express.json())
app.use(cookieParser())

const mm = new MiniMoth({ apiKey: process.env.MINIMOTH_API_KEY })

app.post('/auth/send', async (req, res) => {
  try {
    const { otpId } = await mm.otp.send({ phone: req.body.phone })
    res.json({ otpId })
  } catch (err) {
    if (err instanceof MiniMothError) {
      return res.status(err.statusCode || 400).json({ error: err.code })
    }
    throw err
  }
})

app.post('/auth/verify', async (req, res) => {
  const result = await mm.otp.verify({ phone: req.body.phone, otp: req.body.otp })
  if (!result.valid) return res.status(400).json({ error: result.code })

  res.cookie('mm_refresh', result.refreshToken, { httpOnly: true, secure: true, sameSite: 'strict' })
  res.json({ accessToken: result.accessToken })
})

// safeValidate auto-refreshes expired tokens — no manual refresh logic
async function requireAuth(req, res, next) {
  const token = req.headers['x-access-token']
  if (!token) return res.status(401).json({ error: 'No token' })

  const result = await mm.session.safeValidate(token)
  if (!result.valid) return res.status(401).json({ error: result.code })

  req.sessionId = result.session.sessionId
  req.phone = await lookupPhoneBySessionId(req.sessionId) // your own session_id -> phone mapping, stored at verify time
  if (result.session.newTokens) {
    res.setHeader('X-New-Access-Token', result.session.newTokens.accessToken)
    res.cookie('mm_refresh', result.session.newTokens.refreshToken, { httpOnly: true, secure: true, sameSite: 'strict' })
  }
  next()
}

app.get('/profile', requireAuth, (req, res) => {
  res.json({ phone: req.phone })
})

app.post('/auth/logout', requireAuth, async (req, res) => {
  await mm.session.logout({
    accessToken: req.headers['x-access-token'],
    refreshToken: req.cookies.mm_refresh,
  })
  res.clearCookie('mm_refresh')
  res.json({ ok: true })
})`

export const sdkNextjsCode = `// lib/minimoth.ts
import { MiniMoth } from '@minimoth/sdk-node'
export const mm = new MiniMoth({ apiKey: process.env.MINIMOTH_API_KEY! })

// app/api/auth/send/route.ts
import { mm } from '@/lib/minimoth'
import { MiniMothError } from '@minimoth/sdk-node'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(req: NextRequest) {
  const { phone } = await req.json()
  try {
    const { otpId } = await mm.otp.send({ phone })
    return NextResponse.json({ otpId })
  } catch (err) {
    if (err instanceof MiniMothError) {
      return NextResponse.json({ error: err.code }, { status: err.statusCode || 400 })
    }
    throw err
  }
}

// app/api/auth/verify/route.ts
export async function POST(req: NextRequest) {
  const { phone, otp } = await req.json()
  const result = await mm.otp.verify({ phone, otp })
  if (!result.valid) return NextResponse.json({ error: result.code }, { status: 400 })

  const response = NextResponse.json({ accessToken: result.accessToken })
  response.cookies.set('mm_refresh', result.refreshToken, {
    httpOnly: true, secure: true, sameSite: 'strict', path: '/', maxAge: 60 * 60 * 24 * 10,
  })
  return response
}

// app/api/auth/refresh/route.ts — SDK replaces the raw fetch call
import { cookies } from 'next/headers'

export async function POST() {
  const refreshToken = cookies().get('mm_refresh')?.value
  if (!refreshToken) return NextResponse.json({ error: 'No refresh token' }, { status: 401 })

  try {
    const tokens = await mm.session.refresh(refreshToken)
    const response = NextResponse.json({ accessToken: tokens.accessToken })
    response.cookies.set('mm_refresh', tokens.refreshToken, {
      httpOnly: true, secure: true, sameSite: 'strict', path: '/', maxAge: 60 * 60 * 24 * 10,
    })
    return response
  } catch {
    const response = NextResponse.json({ error: 'SESSION_EXPIRED' }, { status: 401 })
    response.cookies.delete('mm_refresh')
    return response
  }
}

// middleware.ts
import { NextRequest, NextResponse } from 'next/server'

export async function middleware(req: NextRequest) {
  const accessToken = req.cookies.get('mm_access')?.value
  if (!accessToken) return NextResponse.redirect(new URL('/login', req.url))

  const result = await mm.session.safeValidate(accessToken)
  if (!result.valid) return NextResponse.redirect(new URL('/login', req.url))

  const response = NextResponse.next()
  const phone = await lookupPhoneBySessionId(result.session.sessionId) // your own session_id -> phone mapping, stored at verify time
  response.headers.set('x-phone', phone)
  return response
}

export const config = { matcher: ['/dashboard/:path*'] }`

export const nextjsCode = `// app/api/auth/refresh/route.ts  (Next.js App Router)
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export async function POST() {
  const cookieStore = cookies()
  const refreshToken = cookieStore.get('mm_refresh')?.value

  if (!refreshToken) {
    return NextResponse.json({ error: 'No refresh token' }, { status: 401 })
  }

  const res = await fetch('https://api.minimoth.dev/v1/session/refresh', {
    method: 'POST',
    headers: { 'X-Api-Key': process.env.MINIMOTH_API_KEY!, 'Content-Type': 'application/json' },
    body: JSON.stringify({ refresh_token: refreshToken }),
  })

  if (!res.ok) {
    // Refresh token expired or revoked
    const response = NextResponse.json({ error: 'Session expired' }, { status: 401 })
    response.cookies.delete('mm_refresh')
    return response
  }

  const { access_token, refresh_token: newRefreshToken } = await res.json()

  const response = NextResponse.json({ access_token })
  response.cookies.set('mm_refresh', newRefreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    path: '/api/auth/refresh',
    maxAge: 60 * 60 * 24 * 10,   // match your project's refresh window
  })
  return response
}`

export const recipes = [
  { id: 'otp-only', badge: 'Recipe 1', label: 'OTP-only delivery' },
  { id: 'protect-route', badge: 'Recipe 2', label: 'Protecting an API route' },
  { id: 'refresh-loop', badge: 'Recipe 3', label: 'Handling token refresh' },
  { id: 'nextjs', badge: 'Recipe 4', label: 'Next.js App Router — refresh endpoint' },
  { id: 'sdk-express', badge: 'SDK Recipe 1', label: 'Express — full auth flow' },
  { id: 'sdk-nextjs', badge: 'SDK Recipe 2', label: 'Next.js App Router — full auth flow' },
]

Common patterns for integrating MiniMoth into real apps.

<div className="mb-10 border border-white/10 rounded-lg overflow-hidden">
  {recipes.map(({ id, badge, label }, i) => (
    <a
      href={`#${id}`}
      className={`flex items-center gap-3 px-5 py-3 hover:bg-white/5 transition-colors ${i > 0 ? 'border-t border-white/10' : ''}`}
    >
      <span className="text-xs font-mono text-saffron bg-saffron/10 border border-saffron/20 rounded-sm px-2 py-0.5 shrink-0">{badge}</span>
      <span className="text-sm text-ink/80">{label}</span>
    </a>
  ))}
</div>

<div id="otp-only" className="scroll-mt-8">
<div className="flex items-center gap-3 mb-3">
  <span className="text-xs font-mono text-saffron bg-saffron/10 border border-saffron/20 rounded-sm px-2 py-0.5">Recipe 1</span>
  <h2 className="font-sans font-semibold text-xl m-0">OTP-only delivery</h2>
</div>

Use MiniMoth purely to verify a phone number. You handle sessions yourself — with your own JWT, your own database, or whatever auth system you already have. MiniMoth acts as the OTP delivery and verification layer only.

This is a good fit if you already have users in a database and just want to add phone verification, or if you need session behaviour that MiniMoth doesn't support (e.g. multi-device, custom claims in tokens).

<CodeBlock code={otpOnlyCode} lang="javascript" />

<div className="mt-4 bg-white/5 border border-white/10 rounded-lg px-4 py-3 text-xs text-ink/50 leading-relaxed">
  <b className="text-ink/70">What you get:</b> a confirmed phone number. <b className="text-ink/70">What you own:</b> everything after that — sessions, cookies, JWTs, logout.
</div>
</div>

<div id="protect-route" className="mt-14 scroll-mt-8">
<div className="flex items-center gap-3 mb-3">
  <span className="text-xs font-mono text-saffron bg-saffron/10 border border-saffron/20 rounded-sm px-2 py-0.5">Recipe 2</span>
  <h2 className="font-sans font-semibold text-xl m-0">Protecting an API route</h2>
</div>

Verify the `access_token` locally as a JWT in your middleware or route guard — no network round trip per request. The guard's job is just confirming the session is valid and exposing `session_id`; resolving that to an end-user (your own `session_id → phone` mapping, built at `otp/verify` time) is your app's concern, not the guard's.

<CodeBlock code={protectRouteCode} lang="javascript" />

<div className="mt-4 bg-white/5 border border-white/10 rounded-lg px-4 py-3 text-xs text-ink/50 leading-relaxed">
  Local verification is blind to server-side revocation (logout, theft detection) until the token's own 5-minute expiry. If every request needs the latest revocation state, call <code className="font-mono">session/validate</code> instead — see <a href="/docs/sessions#validation-methods" className="text-saffron hover:underline">local vs. API validation</a>.
</div>

The [Node.js SDK](/docs/sdk-nodejs) handles this same JWKS fetch, cache, and local verify for you via `mm.session.safeValidate()` — no need to write it yourself. It also lets you dial revocation freshness up or down per call via `validateMode`: `instant` (local only, the default), `recheck_1m` / `recheck_3m` (revocation check, cached), or `strict` (revocation check every call). See [validateMode](/docs/sdk-nodejs#validatemode).
</div>

<div id="refresh-loop" className="mt-14 scroll-mt-8">
<div className="flex items-center gap-3 mb-3">
  <span className="text-xs font-mono text-saffron bg-saffron/10 border border-saffron/20 rounded-sm px-2 py-0.5">Recipe 3</span>
  <h2 className="font-sans font-semibold text-xl m-0">Handling token refresh</h2>
</div>

The access_token expires after 5 minutes. The pattern below retries any failed request automatically after refreshing — the user never sees a logout or an error.

**Security note:** keep the refresh_token on your server only, in an httpOnly cookie. Never expose it to JavaScript in the browser. The client only ever sees and stores the short-lived access_token.

<CodeBlock code={refreshLoopCode} lang="javascript" />

<div className="mt-4 bg-white/5 border border-white/10 rounded-lg px-4 py-3 text-xs text-ink/50 leading-relaxed">
  The [Node.js SDK](/docs/sdk-nodejs) (<a href="https://www.npmjs.com/package/@minimoth/sdk-node" className="text-saffron hover:underline font-mono">@minimoth/sdk-node</a>) does all of this for you — <code className="font-mono">session.safeValidate()</code> detects an expired access token and refreshes it automatically, no manual retry loop needed. It also offers multiple refresh/revocation modes via <code className="font-mono">validateMode</code>: <code className="font-mono">instant</code>, <code className="font-mono">recheck_1m</code>, <code className="font-mono">recheck_3m</code>, and <code className="font-mono">strict</code> — see <a href="/docs/sdk-nodejs#validatemode" className="text-saffron hover:underline">validateMode</a>.
</div>
</div>

<div id="nextjs" className="mt-14 scroll-mt-8">
<div className="flex items-center gap-3 mb-3">
  <span className="text-xs font-mono text-saffron bg-saffron/10 border border-saffron/20 rounded-sm px-2 py-0.5">Recipe 4</span>
  <h2 className="font-sans font-semibold text-xl m-0">Next.js App Router — refresh endpoint</h2>
</div>

A full server-side refresh route for Next.js. The refresh_token lives in an httpOnly cookie managed by the server; the browser only receives the new short-lived access_token.

<CodeBlock code={nextjsCode} lang="typescript" />

<div className="mt-4 bg-white/5 border border-white/10 rounded-lg px-4 py-3 text-xs text-ink/50 leading-relaxed">
  Set <code className="font-mono">path: '/api/auth/refresh'</code> on the cookie so the browser only sends it to that specific endpoint — not to every API route.
</div>
</div>

<div className="mt-16 mb-4">

## With the Node.js SDK

The same patterns above, using [@minimoth/sdk-node](https://www.npmjs.com/package/@minimoth/sdk-node). The SDK handles phone normalisation, typed errors, and auto-refreshes expired tokens inside `safeValidate()` — no refresh boilerplate needed. [Full SDK guide →](/docs/sdk-nodejs)
</div>

<div id="sdk-express" className="mt-10 scroll-mt-8">
<div className="flex items-center gap-3 mb-3">
  <span className="text-xs font-mono text-saffron bg-saffron/10 border border-saffron/20 rounded-sm px-2 py-0.5">SDK Recipe 1</span>
  <h3 className="font-sans font-semibold text-xl m-0">Express — full auth flow</h3>
</div>

Send, verify, protect routes, and logout — all using the SDK. Auto-refresh is built into `safeValidate()`; new tokens arrive in `session.newTokens` when rotation happens.

<CodeBlock code={sdkExpressCode} lang="typescript" />
</div>

<div id="sdk-nextjs" className="mt-14 scroll-mt-8">
<div className="flex items-center gap-3 mb-3">
  <span className="text-xs font-mono text-saffron bg-saffron/10 border border-saffron/20 rounded-sm px-2 py-0.5">SDK Recipe 2</span>
  <h3 className="font-sans font-semibold text-xl m-0">Next.js App Router — full auth flow</h3>
</div>

Five route handlers and a middleware file. The SDK replaces all raw `fetch` calls to MiniMoth — send, verify, refresh, validate, and logout.

<CodeBlock code={sdkNextjsCode} lang="typescript" />

<div className="mt-4 bg-white/5 border border-white/10 rounded-lg px-4 py-3 text-xs text-ink/50 leading-relaxed">
  For multi-replica or serverless deployments, provide a Redis-backed session store so the SDK can find refresh tokens across instances. See the <a href="/docs/sdk-nodejs#redis" className="text-saffron hover:underline">custom session store</a> section.
</div>
</div>

<div className="mt-14 border border-white/10 rounded-lg px-5 py-5 text-sm text-ink/60 leading-relaxed">
  <p className="text-ink font-medium mb-1">Need a different recipe?</p>
  <p>Email <a href="mailto:info@minimoth.dev" className="text-saffron hover:underline">info@minimoth.dev</a> and we'll add it here.</p>
</div>

---

Full documentation index: https://minimoth.dev/llms.txt
