export const installCode = `npm install @minimoth/sdk-node`

export const setupCode = `import { MiniMoth } from '@minimoth/sdk-node'

const mm = new MiniMoth({
  apiKey: process.env.MINIMOTH_API_KEY,  // mm_live_...
})`

export const sendVerifyCode = `// 1. Send OTP — returns otpId you can use to check delivery status
const { otpId } = await mm.otp.send({ phone: '+919876543210' })

// 2. (Optional) check delivery status
const delivery = await mm.otp.status(otpId)
// delivery.status: 'queued' | 'delivered' | 'failed'
// delivery.channel: 'whatsapp' | 'sms'

// 3. Verify the code — never throws, always returns a result
const result = await mm.otp.verify({ phone: '+919876543210', otp: '123456' })
if (!result.valid) {
  // result.code: 'INVALID_OTP' | 'OTP_NOT_FOUND' | 'VERIFY_RATE_LIMITED' | ...
  return res.status(400).json({ error: result.code })
}

// Phone is verified — result.accessToken, result.refreshToken, result.sessionId
// The SDK stores tokens automatically. On validate(), expired tokens are
// refreshed silently — you never write refresh logic.`

export const expressCode = `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 })

// Send OTP
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
  }
})

// Verify OTP — issue tokens
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 })

  // Keep refresh_token server-side in an httpOnly cookie
  res.cookie('mm_refresh', result.refreshToken, {
    httpOnly: true, secure: true, sameSite: 'strict',
  })
  res.json({ accessToken: result.accessToken })
})

// Auth middleware — safeValidate auto-refreshes if the access token is expired
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

  // SDK auto-refreshed — send new tokens to the client
  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()
}

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

// Logout
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 nextjsLibCode = `// lib/minimoth.ts
import { MiniMoth } from '@minimoth/sdk-node'

export const mm = new MiniMoth({
  apiKey: process.env.MINIMOTH_API_KEY!,
})`

export const nextjsSendCode = `// 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
  }
}`

export const nextjsVerifyCode = `// app/api/auth/verify/route.ts
import { mm } from '@/lib/minimoth'
import { NextRequest, NextResponse } from 'next/server'

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
}`

export const nextjsRefreshCode = `// app/api/auth/refresh/route.ts
// Called by your client when it receives a 401. The SDK handles the
// MiniMoth API call — no manual fetch needed.
import { mm } from '@/lib/minimoth'
import { NextRequest, NextResponse } from 'next/server'
import { cookies } from 'next/headers'

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 })
  }

  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
  }
}`

export const nextjsMiddlewareCode = `// middleware.ts
import { mm } from '@/lib/minimoth'
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))

  // Pass the verified phone to downstream route handlers
  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 nextjsLogoutCode = `// app/api/auth/logout/route.ts
import { mm } from '@/lib/minimoth'
import { NextResponse } from 'next/server'
import { cookies } from 'next/headers'

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

  if (accessToken && refreshToken) {
    await mm.session.logout({ accessToken, refreshToken })
  }

  const response = NextResponse.json({ ok: true })
  response.cookies.delete('mm_access')
  response.cookies.delete('mm_refresh')
  return response
}`

export const validateModeCode = `const mm = new MiniMoth({
  apiKey: process.env.MINIMOTH_API_KEY,
  session: {
    validateMode: 'recheck_1m',  // check revocation, cache result 60 s per token
  },
})`

export const redisStoreCode = `import { createClient } from 'redis'
import { MiniMoth } from '@minimoth/sdk-node'

const redis = createClient({ url: process.env.REDIS_URL })
await redis.connect()

const mm = new MiniMoth({
  apiKey: process.env.MINIMOTH_API_KEY,
  session: {
    store: {
      async getRefreshToken(sessionId) {
        return redis.get(\`mm:rt:\${sessionId}\`)
      },
      async setTokens(sessionId, { refreshToken }) {
        await redis.set(\`mm:rt:\${sessionId}\`, refreshToken, { EX: 60 * 60 * 24 * 10 })
      },
      async deleteSession(sessionId) {
        await redis.del(\`mm:rt:\${sessionId}\`)
      },
    },
  },
})`

export const errorCode = `import { MiniMothError } from '@minimoth/sdk-node'

// send() throws on error
try {
  await mm.otp.send({ phone })
} catch (err) {
  if (err instanceof MiniMothError) {
    switch (err.code) {
      case 'OTP_RATE_LIMITED':      return res.status(429).json({ error: 'Wait 10 minutes before requesting another OTP' })
      case 'INSUFFICIENT_BALANCE':  return res.status(402).json({ error: 'Top up your wallet at app.minimoth.dev' })
      case 'INVALID_PHONE':         return res.status(422).json({ error: 'Invalid phone number' })
      default:                      throw err
    }
  }
  throw err
}

// verify() never throws — returns { valid: false, code } on failure
const result = await mm.otp.verify({ phone, otp })
if (!result.valid) {
  switch (result.code) {
    case 'INVALID_OTP':          return res.status(400).json({ error: 'Wrong code' })
    case 'OTP_NOT_FOUND':        return res.status(400).json({ error: 'OTP expired or already used' })
    case 'VERIFY_RATE_LIMITED':  return res.status(429).json({ error: 'Too many attempts — request a new OTP' })
  }
}`

Official SDK for Node.js, Express, and Next.js. Handles phone normalisation, token management, local JWT validation, and auto-refresh. Published as <a href="https://www.npmjs.com/package/@minimoth/sdk-node" target="_blank" rel="noopener noreferrer">@minimoth/sdk-node</a> on npm.

<div className="mb-10">
  <Callout variant="accent">
    <p><span className="text-saffron font-medium">Unlike raw API calls, you never write refresh logic.</span> The SDK's <code className="font-mono text-xs text-ink/80">session.validate()</code> automatically detects expired access tokens and refreshes them using the stored refresh token — returning the new tokens in <code className="font-mono text-xs text-ink/80">session.newTokens</code> so you can forward them to the client. One call, no manual token rotation.</p>
  </Callout>
</div>

## Installation

<CodeBlock code={installCode} lang="bash" />

Requires Node.js 18+. View the package on <a href="https://www.npmjs.com/package/@minimoth/sdk-node" target="_blank" rel="noopener noreferrer">npm</a>.

## Setup

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

## OTP flow

Send an OTP, optionally check delivery, then verify the code.

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

<h2 id="express" className="font-medium text-xl mt-14 mb-3 scroll-mt-8">Express — complete example</h2>

A single Express server handling the full auth flow. The SDK's default in-memory store persists refresh tokens across requests — auto-refresh works with no extra setup.

<CodeBlock code={expressCode} 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 space-y-2">
  <p><b className="text-ink/70">How auto-refresh works here:</b> when <code className="font-mono">safeValidate</code> finds an expired access token, it silently calls <code className="font-mono">session/refresh</code> using the stored refresh token and returns the new tokens in <code className="font-mono">session.newTokens</code>. Your middleware forwards them via <code className="font-mono">X-New-Access-Token</code> header and a new cookie. The user never sees a logout.</p>
  <p><b className="text-ink/70">Client must handle <code className="font-mono">X-New-Access-Token</code>:</b> your browser-side fetch wrapper must check every response for this header and update the stored access token when it appears. If the client keeps sending the old expired token, the SDK will auto-refresh on every request — burning a network round-trip each time. See <a href="/docs/recipes#refresh-loop" className="text-saffron hover:underline">Recipe 3</a> for the client-side pattern.</p>
</div>

<h2 id="nextjs" className="font-medium text-xl mt-14 mb-3 scroll-mt-8">Next.js App Router — complete example</h2>

Five files cover the full auth lifecycle. The refresh token lives in an httpOnly cookie — the client only ever sees the short-lived access token.

### Shared client

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

### Send OTP

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

### Verify OTP

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

### Refresh endpoint

Called by your client when it receives a 401. The SDK replaces the raw `fetch` to `session/refresh`.

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

### Middleware

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

### Logout

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

<h2 id="validatemode" className="font-medium text-xl mt-14 mb-3 scroll-mt-8">validateMode</h2>

Controls whether `session.validate()` checks revocation against the server.

| Mode | What it does | Use when |
|---|---|---|
| `instant` | Local JWT verify only — zero network round trip | Default. Fine for most apps. |
| `recheck_1m` | Revocation check, cached 60 s per token | Sensitive routes (payments, admin). |
| `recheck_3m` | Revocation check, cached 180 s per token | Balance between freshness and latency. |
| `strict` | Revocation check on every call, no cache | High-security endpoints. |

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

<h2 id="redis" className="font-medium text-xl mt-14 mb-3 scroll-mt-8">Custom session store (Redis)</h2>

The default store is in-memory — it works well for single-process Express apps. For multi-replica deployments or Next.js, provide a Redis-backed store so refresh tokens are shared across instances and auto-refresh works reliably.

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

## Error handling

`send()` throws `MiniMothError` on failure. `verify()` never throws — it always returns a result object. See [Error Codes](/docs/error-codes) for the full list.

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

<div className="mt-10 border border-white/10 rounded-lg px-5 py-4 text-sm text-ink/60 leading-relaxed">
  <p><span className="text-ink font-medium">Not using Node.js?</span> Use the <a href="/docs/quickstart" className="text-saffron hover:underline">REST API directly</a> — MiniMoth is a plain HTTP API and works with any language or framework.</p>
</div>

---

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