Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Explains the standardized API organization pattern for this codebase. Use when creating new API endpoints, API clients, or modifying existing API structure. Covers the 5-file system (endpoint-types, endpoints, api-client, admin-api-client, protected-endpoints), role-based access patterns (admin vs regular users), and TypeScript type safety across the API layer. All API code lives in src/lib/api/ following this exact pattern.
.claude/skills/aiskillstore-api-organization/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 120% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 61% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 66% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 119% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 101% | 0% |
This skill defines the standardized API organization pattern used throughout this codebase. All external API integrations follow the same 5-file structure for consistency, type safety, and maintainability.
Use this skill when:
endpoints.tsAll API code lives in src/lib/api/ with exactly these files:
src/lib/api/
├── endpoint-types.ts # TypeScript types for all endpoints
├── endpoints.ts # URL definitions organized by domain
├── api-client.ts # Generic authenticated API client
├── admin-api-client.ts # Admin-only API client with role checks
└── protected-endpoints.ts # Type-safe wrapper functions1. endpoint-types.ts
See references/endpoint-types-pattern.md for detailed structure.
2. endpoints.ts
See references/endpoints-pattern.md for detailed structure.
3. api-client.ts
See references/api-client-pattern.md for implementation.
4. admin-api-client.ts
See references/admin-api-client-pattern.md for implementation.
5. protected-endpoints.ts
import { api } from '@/lib/api/protected-endpoints'See references/protected-endpoints-pattern.md for detailed structure.
This application uses Supabase Auth for authentication. All API requests require authentication via Supabase access tokens.
src/lib/supabase/
├── client.ts # Browser-side Supabase client
├── server.ts # Server-side Supabase client (RSC, Server Actions)
└── middleware.ts # Middleware helper for auth cookie refreshcreateClient() from server.tsSee references/supabase-auth-integration.md for detailed implementation.
Use api-client.ts functions with automatic Supabase auth:
typescriptimport { apiGet, apiPost } from '@/lib/api/api-client'; // Access token extracted from Supabase session automatically const data = await apiGet<ResponseType>(url);
Use admin-api-client.ts functions with role validation:
typescriptimport { adminApiRequest, checkAdminPermission } from '@/lib/api/admin-api-client'; // Validate admin access first (checks user role from database) await checkAdminPermission(); // Throws if not admin // Make admin API request const data = await adminApiRequest<ResponseType>(url, options);
Admin roles are stored in the users table:
users.role = 'admin' - Standard admin accessusers.role = 'member' - Regular user accessFirst user in a family is automatically assigned admin role.
Follow this exact order when integrating a new API into the application:
typescript// 1. Define response type(s) export interface ResourceItem { id: string; name: string; // ... other fields from your API response } // 2. Define request DTO(s) for mutations export interface CreateResourceDto { name: string; // ... fields required to create } // 3. Add parameter types to EndpointParams interface export interface EndpointParams { // ... existing domains resources: { list: void; // No params needed get: { id: string }; // Requires ID create: void; // Body in request, not params update: { id: string }; delete: { id: string }; }; } // 4. Add response types to EndpointResponses interface export interface EndpointResponses { // ... existing domains resources: { list: ResourceItem[]; get: ResourceItem; create: ResourceItem; update: ResourceItem; delete: void; }; } // 5. Add request body types to EndpointBodies interface (if needed) export interface EndpointBodies { // ... existing domains resources: { create: CreateResourceDto; update: CreateResourceDto; }; }
typescriptexport const API_ENDPOINTS = { // ... existing categories resources: { list: () => `${API_BASE}/api/resources`, get: (id: string) => `${API_BASE}/api/resources/${id}`, create: () => `${API_BASE}/api/resources`, update: (id: string) => `${API_BASE}/api/resources/${id}`, delete: (id: string) => `${API_BASE}/api/resources/${id}`, }, };
typescriptexport const api = { // ... existing domains resources: { async list(): Promise<ResourceItem[]> { return apiGet<ResourceItem[]>( API_ENDPOINTS.resources.list() ); }, async get(id: string): Promise<ResourceItem> { return apiGet<ResourceItem>( API_ENDPOINTS.resources.get(id) ); }, async create(data: CreateResourceDto): Promise<ResourceItem> { return apiPost<ResourceItem, CreateResourceDto>( API_ENDPOINTS.resources.create(), data ); }, async update(id: string, data: CreateResourceDto): Promise<ResourceItem> { return apiPut<ResourceItem, CreateResourceDto>( API_ENDPOINTS.resources.update(id), data ); }, async delete(id: string): Promise<void> { return apiDelete<void>( API_ENDPOINTS.resources.delete(id) ); }, }, };
typescript'use server'; import { api } from '@/lib/api/protected-endpoints'; // In a server component or server action const resources = await api.resources.list(); const resource = await api.resources.get(id); const newResource = await api.resources.create({ name: 'New Resource' });
list / listAll - GET multiple itemsget - GET single itemcreate - POST new itemupdate - PUT/PATCH existing itemdelete - DELETE itemAudienceListItem)CreateUserDto, UpdateSettingsDto)All API clients handle errors automatically:
typescripttry { const data = await api.myFeature.get(id); } catch (error) { // Error already parsed and formatted console.error('API error:', error.message); }
Common error types:
AdminAuthError - Admin permission deniedAdminApiError - Admin API request failedapi-client with parsed messagesapi.resources.list())getAuthHeaders()typescript const supabase = await createClient(); const { data: { session } } = await supabase.auth.getSession(); const accessToken = session?.access_token;
Same flow as above, but with additional role check:
checkAdminPermission() firstusers table for current user's roleAdminAuthError if role is not 'admin'API_ENDPOINTSapiGet, apiPost, etc. handle authcheckAdminPermission() firstNEXT_PUBLIC_SUPABASE_URL - Supabase project URLNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY - Supabase anonymous public keyINSTANCE_API_URL or API_URL - Base URL for external API endpointsNEXT_PUBLIC_SITE_URL - Site URL for redirects (optional, defaults to localhost:3000)If migrating existing API code to this pattern:
endpoints.tsendpoint-types.ts for params/responsesapiGet/apiPost/etcprotected-endpoints.tsapi objectExample migration:
typescript// Before (scattered fetch calls) const response = await fetch(`${API_BASE}/api/resources/${id}`, { headers: { Authorization: `Bearer ${token}` } }); const resource = await response.json(); // After (centralized pattern) import { api } from '@/lib/api/protected-endpoints'; const resource = await api.resources.get(id); // Auth automatic, types included
See reference files for detailed implementation patterns:
references/supabase-auth-integration.md - Supabase auth setup and integrationreferences/endpoint-types-pattern.md - Type definition structurereferences/endpoints-pattern.md - URL organization patternreferences/api-client-pattern.md - Generic client implementation with Supabasereferences/admin-api-client-pattern.md - Admin client with role-based accessreferences/protected-endpoints-pattern.md - Wrapper function patterns| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | fail→pass | 17,316 | 9,183 | -47% | 1 | 1 | 0% | 2,079 | 4,568 | +120% | 0 | 0 | — |
case-04 | fail→pass | 20,804 | 12,850 | -38% | 1 | 1 | 0% | 2,763 | 4,438 | +61% | 0 | 0 | — |
case-05 | fail→pass | 17,550 | 9,305 | -47% | 1 | 1 | 0% | 2,797 | 4,642 | +66% | 0 | 0 | — |
case-06 | fail→pass | 15,584 | 10,657 | -32% | 1 | 1 | 0% | 1,828 | 4,003 | +119% | 0 | 0 | — |
case-01 | fail→pass | 15,806 | 16,253 | +3% | 1 | 1 | 0% | 2,774 | 5,580 | +101% | 0 | 0 | — |
case-02 | fail→pass | 29,670 | 14,747 | -50% | 1 | 1 | 0% | 3,545 | 5,903 | +67% | 0 | 0 | — |
case-03 | fail→pass | 21,031 | 19,247 | -8% | 1 | 1 | 0% | 4,494 | 6,250 | +39% | 0 | 0 | — |
case-08 | fail→fail | 19,530 | 21,573 | +10% | 1 | 1 | 0% | 2,493 | 5,461 | +119% | 0 | 0 | — |
case-09 | fail→pass | 15,517 | 12,622 | -19% | 1 | 1 | 0% | 2,371 | 4,927 | +108% | 0 | 0 | — |
case-10 | pass→pass | 16,117 | 3,424 | -79% | 1 | 1 | 0% | 1,613 | 3,461 | +115% | 0 | 0 | — |
case-11 | fail→pass | 17,446 | 10,409 | -40% | 1 | 1 | 0% | 2,212 | 3,842 | +74% | 0 | 0 | — |
case-12 | fail→pass | 6,785 | 8,563 | +26% | 1 | 1 | 0% | 1,242 | 3,519 | +183% | 0 | 0 | — |
case-13 | fail→pass | 10,874 | 3,896 | -64% | 1 | 1 | 0% | 1,919 | 3,424 | +78% | 0 | 0 | — |
case-14 | fail→pass | 17,273 | 16,576 | -4% | 1 | 1 | 0% | 2,979 | 4,943 | +66% | 0 | 0 | — |
case-15 | fail→pass | 26,175 | 13,403 | -49% | 1 | 1 | 0% | 2,608 | 5,098 | +95% | 0 | 0 | — |
case-16 | fail→pass | 7,117 | 5,431 | -24% | 1 | 1 | 0% | 1,261 | 3,824 | +203% | 0 | 0 | — |
case-17 | fail→pass | 11,650 | 5,590 | -52% | 1 | 1 | 0% | 2,070 | 3,960 | +91% | 0 | 0 | — |
case-18 | fail→fail | 15,011 | 7,535 | -50% | 1 | 1 | 0% | 2,204 | 4,339 | +97% | 0 | 0 | — |
case-19 | fail→pass | 20,612 | 16,086 | -22% | 1 | 1 | 0% | 2,782 | 4,984 | +79% | 0 | 0 | — |
case-20 | pass→pass | 12,562 | 6,030 | -52% | 1 | 1 | 0% | 1,455 | 3,869 | +166% | 0 | 0 | — |
case-21 | pass→pass | 15,328 | 12,448 | -19% | 1 | 1 | 0% | 1,964 | 4,416 | +125% | 0 | 0 | — |
case-22 | pass→pass | 18,945 | 20,072 | +6% | 1 | 1 | 0% | 2,730 | 5,725 | +110% | 0 | 0 | — |
case-23 | pass→pass | 11,036 | 9,573 | -13% | 1 | 1 | 0% | 1,049 | 3,666 | +249% | 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. 23 cases were attempted. The headline lift of +70 percentage points is the difference between those two pass rates over the 23 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.