ContentForger

Tutorials

Tutorial: Add Anthropic API Calls to Your ContentForger App

Add Claude-powered features to any ContentForger output in about ten minutes: the API route, streaming, rate limiting, model choice and a monthly budget.

By Thomas Lovas Lokoy — 2026-04-28, last updated 2026-10-19

Close-up of a circuit board with chips
Photo: Circuit Board by geerlingguy · CC BY-SA 2.0 · cropped
On this page (11)
  1. What you are building
  2. Step one: install the SDK
  3. Step two: get an API key
  4. Step three: set a monthly budget
  5. Step four: create the API route
  6. Step five: add rate limiting
  7. Step six: the frontend
  8. Step seven: add authentication if needed
  9. Model choice
  10. Cost tracking
  11. What you just built

ContentForger generates apps. Sometimes you want those apps to have their own AI features. Here is how to add Claude API calls to any ContentForger-generated project.

What you are building

A simple endpoint that takes a prompt, calls Claude, streams the response back to the frontend. Works for chatbots, content generators, analysis tools, anything.

Step one: install the SDK

In your ContentForger project:

pnpm add @anthropic-ai/sdk

Step two: get an API key

Go to console.anthropic.com. Sign up if you have not. Navigate to API Keys. Create a new key.

Add to .env.local:

ANTHROPIC_API_KEY=sk-ant-api03-your-key-here

Step three: set a monthly budget

This is the step people skip and regret. In the Anthropic console, go to Plans and Billing. Set a monthly spending limit. Start with fifty dollars.

If a bug sends your API into a loop, this cap saves you. Do not skip this step.

Step four: create the API route

Create app/api/claude/route.ts:

import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY!
})

export async function POST(req: Request) {
  const { prompt } = await req.json()

  const encoder = new TextEncoder()
  const stream = new ReadableStream({
    async start(controller) {
      const response = await client.messages.stream({
        model: 'claude-sonnet-5-5',
        max_tokens: 2000,
        messages: [{ role: 'user', content: prompt }]
      })

      for await (const chunk of response) {
        if (chunk.type === 'content_block_delta' && chunk.delta.type === 'text_delta') {
          controller.enqueue(encoder.encode(chunk.delta.text))
        }
      }
      controller.close()
    }
  })

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/plain; charset=utf-8',
      'Transfer-Encoding': 'chunked'
    }
  })
}

Step five: add rate limiting

Do not skip this. A public API endpoint that calls Anthropic will be abused within days.

Simple in-memory rate limit. Add to the top of route.ts:

const counts = new Map<string, { count: number; reset: number }>()

function rateLimit(ip: string, max = 20, window = 3600000) {
  const now = Date.now()
  const record = counts.get(ip)
  if (!record || record.reset < now) {
    counts.set(ip, { count: 1, reset: now + window })
    return true
  }
  if (record.count >= max) return false
  record.count++
  return true
}

In your POST handler:

const ip = req.headers.get('x-forwarded-for') ?? 'anonymous'
if (!rateLimit(ip)) {
  return new Response('Rate limit exceeded', { status: 429 })
}

This allows twenty requests per hour per IP. Adjust to taste.

Step six: the frontend

Create a page or component that calls the endpoint and displays the streaming response:

'use client' import { useState } from 'react'

export default function ClaudePage() {
  const [prompt, setPrompt] = useState('')
  const [response, setResponse] = useState('')
  const [loading, setLoading] = useState(false)

  async function handleSubmit() {
    setLoading(true)
    setResponse('')

    const res = await fetch('/api/claude', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ prompt })
    })

    const reader = res.body!.getReader()
    const decoder = new TextDecoder()

    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      setResponse(prev => prev + decoder.decode(value))
    }

    setLoading(false)
  }

  return (
    <div>
      <textarea value={prompt} onChange={e => setPrompt(e.target.value)} />
      <button onClick={handleSubmit} disabled={loading}>
        {loading ? 'Thinking...' : 'Send'}
      </button>
      <div>{response}</div>
    </div>
  )
}

Step seven: add authentication if needed

If you want only logged-in users to call this endpoint, check the Supabase Auth session at the top of the route. ContentForger apps sign users in with Supabase Auth; the server client below is the one from the Supabase Auth tutorial, and your app may keep it under a different path.

import { createClient } from '@/lib/supabase/server'

export async function POST(req: Request) {
  const supabase = await createClient()
  const { data } = await supabase.auth.getClaims()
  if (!data?.claims?.sub) return new Response('Unauthorized', { status: 401 })

  // ... rest of your handler
}

Model choice

Haiku is fastest and cheapest. Use for simple tasks, short responses, high volume. Sonnet is the sensible default. Good quality, reasonable cost. Opus is most capable but costly. Use for complex reasoning or when output quality justifies the spend.

For a user-facing chatbot, start with Sonnet. Switch down to Haiku for features where quality matters less. Switch to Opus only for premium tier features.

Cost tracking

Log input and output tokens for every call. Cheap to do, saves you from billing surprises.

Most serious AI apps maintain a per-user token counter in the database and enforce monthly caps. If you are building a paid product, do this from day one.

What you just built

A streaming Claude endpoint in a ContentForger app. Same pattern works for any Claude-powered feature. Chat interfaces, content generation, summarisation, analysis. Swap the system prompt and the UI.

Total time: under ten minutes if you move quickly. The pattern is the same for every AI feature you will ever add.

ShareXLinkedInFacebook

Was this article helpful?

New guides in your inbox

One short email when we publish something useful — tutorials, comparisons and launch checklists. Unsubscribe in one click.

Related articles

Tutorials

Tutorial: Supabase Auth in Your ContentForger App

How sign-in works in a ContentForger app: Supabase Auth through @supabase/ssr, the server client, session refresh, user_id with RLS, and a two-user test.

Thomas Lovas Lokoy · 7 min read · 2026-04-28