Install any skill in seconds. Free to start, no credit card required.
Get Started Free →API route implementation patterns with RLS, validation, and error handling. Use when creating API routes, implementing CRUD endpoints, adding server-side validation, handling webhooks, or implementing error handling patterns. Do NOT use for frontend-only changes or database migrations without API involvement.
.claude/skills/bybren-llc-api-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-10 | ✗→✓ | ▲ Improved | 10% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 42% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 30% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 40% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 31% | 0% |
Route to existing API patterns and provide checklists for safe, validated API route implementation. All API routes MUST use RLS context helpers—see rls-patterns skill.
| Pattern | Location | Purpose | | ----------------- | --------------------------------------------- | --------------------------- | | User Context API | patterns_library/api/user-context-api.md | User-scoped operations | | Admin Context API | patterns_library/api/admin-context-api.md | Admin-scoped operations | | Zod Validation | patterns_library/api/zod-validation-api.md | Request/response validation | | Webhook Handler | patterns_library/api/webhook-handler.md | Webhook processing | | Bonus Content | patterns_library/api/bonus-content-delivery.md | Protected content delivery |
typescript// FORBIDDEN: Direct Prisma calls (bypass RLS) const users = await prisma.user.findMany(); // Must use: withUserContext, withAdminContext, or withSystemContext // FORBIDDEN: Missing authentication check export async function GET(req: Request) { return getUserData(); // No auth check! } // FORBIDDEN: Unvalidated user input const { userId } = await req.json(); // Must validate with Zod schema // FORBIDDEN: Generic error responses return new Response("Error", { status: 500 }); // Must use structured error response
typescript// CORRECT: RLS context + auth check export async function GET(req: Request) { const { userId } = await auth(); if (!userId) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const data = await withUserContext(prisma, userId, async (client) => { return client.user.findUnique({ where: { user_id: userId } }); }); return NextResponse.json(data); } // CORRECT: Zod validation const schema = z.object({ email: z.string().email(), name: z.string().min(1), }); const result = schema.safeParse(body); if (!result.success) { return NextResponse.json( { error: "Validation failed", details: result.error.flatten() }, { status: 400 }, ); }
Before ANY API route:
await auth() from auth providerwithUserContext/withAdminContext/withSystemContext)typescriptreturn NextResponse.json({ data, success: true }, { status: 200 });
typescriptreturn NextResponse.json( { error: "Human-readable error message", code: "ERROR_CODE", details: optional_details, }, { status: 400 | 401 | 403 | 404 | 500 }, );
| Code | When to Use | | ---- | -------------------------------------------- | | 200 | Success | | 201 | Created (POST) | | 400 | Bad request / validation error | | 401 | Not authenticated | | 403 | Forbidden (authenticated but not authorized) | | 404 | Resource not found | | 500 | Server error |
typescriptimport { auth } from "@clerk/nextjs/server"; import { NextResponse } from "next/server"; import { z } from "zod"; import { withUserContext } from "@/lib/rls-helpers"; import { prisma } from "@/lib/prisma"; // Request validation schema const RequestSchema = z.object({ // Define expected fields }); export async function POST(req: Request) { try { // 1. Authenticate const { userId } = await auth(); if (!userId) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } // 2. Parse and validate request const body = await req.json(); const result = RequestSchema.safeParse(body); if (!result.success) { return NextResponse.json( { error: "Validation failed", details: result.error.flatten() }, { status: 400 }, ); } // 3. Execute with RLS context const data = await withUserContext(prisma, userId, async (client) => { return client.resource.create({ data: result.data }); }); // 4. Return success response return NextResponse.json({ data, success: true }, { status: 201 }); } catch (error) { console.error("API error:", error); return NextResponse.json( { error: "Internal server error" }, { status: 500 }, ); } }
For documenting new endpoints:
markdown## Endpoint: POST /api/resource ### Description Creates a new resource for the authenticated user. ### Authentication Required: Session authentication ### Request Body | Field | Type | Required | Description | | ----- | ------ | -------- | ------------- | | name | string | Yes | Resource name | | type | string | No | Resource type | ### Response **Success (201)**: \`\`\`json { "data": { "id": 1, "name": "..." }, "success": true } \`\`\` **Error (400)**: \`\`\`json { "error": "Validation failed", "details": {...} } \`\`\` ### RLS Context Uses `withUserContext` - user can only access own resources.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-10 | fail→pass | 13,252 | 4,524 | -66% | 1 | 1 | 0% | 2,132 | 2,351 | +10% | 0 | 0 | — |
case-01 | fail→pass | 11,817 | 8,505 | -28% | 1 | 1 | 0% | 2,396 | 3,392 | +42% | 0 | 0 | — |
case-02 | fail→pass | 10,303 | 5,000 | -51% | 1 | 1 | 0% | 1,963 | 2,546 | +30% | 0 | 0 | — |
case-03 | fail→pass | 15,769 | 13,305 | -16% | 1 | 1 | 0% | 3,251 | 4,550 | +40% | 0 | 0 | — |
case-04 | fail→fail | 7,531 | 5,637 | -25% | 1 | 1 | 0% | 1,224 | 2,539 | +107% | 0 | 0 | — |
case-05 | pass→pass | 10,028 | 10,389 | +4% | 1 | 1 | 0% | 1,805 | 3,635 | +101% | 0 | 0 | — |
case-06 | pass→pass | 8,227 | 6,243 | -24% | 1 | 1 | 0% | 1,415 | 2,619 | +85% | 0 | 0 | — |
case-07 | fail→pass | 10,595 | 5,008 | -53% | 1 | 1 | 0% | 1,984 | 2,607 | +31% | 0 | 0 | — |
case-08 | fail→pass | 16,209 | 8,899 | -45% | 1 | 1 | 0% | 3,242 | 3,202 | -1% | 0 | 0 | — |
case-09 | pass→pass | 12,003 | 6,671 | -44% | 1 | 1 | 0% | 2,050 | 2,703 | +32% | 0 | 0 | — |
case-11 | fail→pass | 13,090 | 6,523 | -50% | 1 | 1 | 0% | 2,360 | 2,780 | +18% | 0 | 0 | — |
case-12 | fail→pass | 7,526 | 4,579 | -39% | 1 | 1 | 0% | 1,383 | 2,332 | +69% | 0 | 0 | — |
case-13 | pass→pass | 9,467 | 2,904 | -69% | 1 | 1 | 0% | 1,727 | 2,027 | +17% | 0 | 0 | — |
case-14 | pass→pass | 4,504 | 2,443 | -46% | 1 | 1 | 0% | 668 | 1,951 | +192% | 0 | 0 | — |
case-15 | pass→pass | 10,154 | 3,846 | -62% | 1 | 1 | 0% | 1,733 | 2,298 | +33% | 0 | 0 | — |
case-16 | pass→pass | 13,533 | 6,250 | -54% | 1 | 1 | 0% | 2,413 | 2,766 | +15% | 0 | 0 | — |
case-17 | fail→pass | 12,946 | 8,331 | -36% | 1 | 1 | 0% | 2,086 | 3,186 | +53% | 0 | 0 | — |
case-18 | fail→pass | 8,378 | 1,594 | -81% | 1 | 1 | 0% | 1,268 | 1,806 | +42% | 0 | 0 | — |
case-19 | pass→pass | 6,545 | 2,972 | -55% | 1 | 1 | 0% | 1,017 | 2,011 | +98% | 0 | 0 | — |
case-20 | fail→pass | 11,971 | 6,485 | -46% | 1 | 1 | 0% | 2,016 | 2,711 | +34% | 0 | 0 | — |
case-21 | pass→pass | 14,759 | 3,912 | -73% | 1 | 1 | 0% | 2,448 | 2,313 | -6% | 0 | 0 | — |
case-22 | pass→pass | 11,469 | 3,794 | -67% | 1 | 1 | 0% | 1,786 | 2,190 | +23% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +50 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.