API Routes & System Prompts
How to create robust Expo API routes with expert-level system prompts for your app's AI features. All API routes run server-side — API keys stay secure and never reach the client.
.env file (without the EXPO_PUBLIC_ prefix) are only accessible in API route files. They are never bundled into the client app.Route Architecture
src/app/api/
├── [feature]+api.ts ← Your app's core AI feature
├── vision+api.ts ← Image analysis (boilerplate)
├── chat+api.ts ← General AI chat (boilerplate)
├── generate+api.ts ← Text/image generation (boilerplate)
├── transcribe+api.ts ← Audio transcription (boilerplate)
└── health+api.ts ← Health check endpointYour custom route is the [feature]+api.ts file — this is where your app's unique AI functionality lives with its expert system prompt.
API Route Template
const API_KEY = process.env.OPENAI_API_KEY; // or ANTHROPIC_API_KEY
interface FeatureRequest {
inputText: string;
mode?: string;
options?: Record<string, unknown>;
}
const SYSTEM_PROMPT = `You are [EXPERT ROLE] specializing in [DOMAIN].
Your Role:
- [Primary responsibility]
- [Secondary responsibility]
- [Constraints and guidelines]
Output Rules:
- You MUST respond with ONLY valid JSON
- [Specific formatting rules]
You MUST respond with ONLY valid JSON in this exact format:
{
"result": "string — the main output",
"confidence": 0.0-1.0,
"details": { ... },
"disclaimer": "string — if applicable"
}`;
export async function POST(request: Request): Promise<Response> {
try {
const body: FeatureRequest = await request.json();
const { inputText } = body;
if (!inputText?.trim()) {
return Response.json({ error: "Input text is required" }, { status: 400 });
}
if (!API_KEY) {
return Response.json({ error: "API key not configured" }, { status: 500 });
}
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: SYSTEM_PROMPT },
{ role: "user", content: inputText },
],
response_format: { type: "json_object" },
temperature: 0.7,
max_tokens: 2000,
}),
});
const data = await response.json();
const result = JSON.parse(data.choices[0].message.content);
return Response.json(result);
} catch (error) {
console.error("[API] Error:", error);
return Response.json({ error: "Internal server error" }, { status: 500 });
}
}Anthropic (Claude) Variant
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.ANTHROPIC_API_KEY!,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify({
model: "claude-sonnet-4-20250514",
max_tokens: 2000,
system: SYSTEM_PROMPT,
messages: [{ role: "user", content: inputText }],
}),
});
const data = await response.json();
const result = JSON.parse(data.content[0].text);Vision (Image Analysis) Variant
For camera/identifier apps — send base64 image to GPT-4o:
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
model: "gpt-4o",
messages: [
{ role: "system", content: SYSTEM_PROMPT },
{
role: "user",
content: [
{ type: "text", text: userPrompt },
{
type: "image_url",
image_url: { url: `data:image/jpeg;base64,${base64Image}` },
},
],
},
],
response_format: { type: "json_object" },
max_tokens: 2000,
}),
});Health Check Endpoint
Always include a health check route to verify your API routes are deployed and API keys are configured. This is the first thing to test after eas deploy.
export function GET() {
const hasOpenAI = !!process.env.OPENAI_API_KEY;
const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
return Response.json({
status: "ok",
timestamp: new Date().toISOString(),
keys: {
openai: hasOpenAI ? "configured" : "MISSING",
anthropic: hasAnthropic ? "configured" : "MISSING",
},
});
}# After deploying, verify:
curl https://your-app.expo.app/api/healthInput Truncation Guard
Prevent runaway API costs by truncating long user input before sending to the AI provider. This is especially important for text tools where users can paste large amounts of text.
const MAX_INPUT_LENGTH = 5000; // characters
export async function POST(request: Request): Promise<Response> {
const body = await request.json();
let { inputText } = body;
if (!inputText?.trim()) {
return Response.json({ error: "Input text is required" }, { status: 400 });
}
// Truncate to prevent excessive API costs
if (inputText.length > MAX_INPUT_LENGTH) {
inputText = inputText.slice(0, MAX_INPUT_LENGTH);
}
// ... rest of API call
}Environment Variables
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...These are server-side only — no EXPO_PUBLIC_ prefix. They are only accessible in API routes (src/app/api/), never in client code.
System Prompt Examples
Text Rewriter
You are an expert linguistic transformer specializing in text rewriting and paraphrasing.
Your Role:
- Rewrite the given text in the specified mode while preserving the original meaning
- Maintain the same language as the input
- Never add commentary — return ONLY the rewritten text
Modes: FORMAL, CASUAL, ACADEMIC, CREATIVE, SIMPLIFY, EXPAND, SUMMARIZE
You MUST respond with ONLY valid JSON:
{"rewrittenText": "...", "mode": "...", "wordCount": 0}Fish/Snake Identifier (Camera App)
You are Fishify's marine biologist AI. You identify fish species from photographs.
Identification Methodology:
1. Body shape analysis
2. Coloration and pattern
3. Fin configuration
4. Scale type and pattern
Confidence Guide:
- 90-100%: Distinctive species with clear features visible
- 70-89%: Strong match but some features obscured
- Below 50%: Insufficient detail
You MUST respond with ONLY valid JSON:
{
"species": "Common Name",
"scientificName": "Genus species",
"confidence": 0.0-1.0,
"description": "Brief description",
"funFact": "Interesting fact"
}Model Selection Guide
| Provider | Model | Best For | Cost |
|---|---|---|---|
| OpenAI | gpt-4o-mini | Most text tasks, fast, cheap | $0.15/1M input |
| OpenAI | gpt-4o | Vision/image analysis, complex reasoning | $2.50/1M input |
| Anthropic | claude-sonnet-4 | Long-form writing, nuanced text | $3/1M input |
Quick Rules
Identifier apps (camera): use gpt-4o for vision. Text tools (rewrite, detect): use gpt-4o-mini or Claude Sonnet. Creative writing: use Claude for better prose. Simple utilities: use gpt-4o-mini for speed.
System Prompt Quality Checklist
Before shipping, verify your system prompt includes:
| Element | Example |
|---|---|
| Role defined | "You are [expert] specializing in [domain]" |
| Methodology described | Step-by-step how AI should approach the task |
| Output format exact | JSON schema with field types and descriptions |
| Confidence scoring | When should the AI be more/less confident? |
| Safety disclaimers | Medical, legal, financial — where applicable |
| 200+ words minimum | Short prompts produce generic results |
Expert system prompts for 10+ app categories
Proven prompts for text tools, camera/identifier apps, and utilities — all included.