.claude/skills/entity-api/SKILL.md
Dynamic entity API patterns for CRUD operations. Covers entity resolution, query parameters, response formats, child entities, and metadata. Use this skill when consuming entity APIs or understanding dynamic endpoint behavior.
npx skillsauth add NextSpark-js/nextspark entity-apiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Patterns for consuming the dynamic entity API system.
app/api/v1/
├── [entity]/route.ts # LIST (GET), CREATE (POST)
├── [entity]/[id]/route.ts # READ (GET), UPDATE (PATCH), DELETE
├── [entity]/[id]/child/[childType]/route.ts # Child LIST/CREATE
└── [entity]/[id]/child/[childType]/[childId]/route.ts # Child UPDATE/DELETE
core/lib/api/entity/
├── generic-handler.ts # CRUD handlers
├── resolver.ts # Entity resolution
└── helpers.ts # Metadata, response formatting
When a request hits /api/v1/{entity}:
/api/v1/products → productsentityRegistry.getBySlug('products')app/api/v1/(contents)/products/route.ts{ entityConfig, hasCustomOverride, isValidEntity }// If custom override exists, generic handler is skipped
if (existsSync('app/api/v1/(contents)/products/route.ts')) {
return { hasCustomOverride: true, isValidEntity: false }
}
GET /api/v1/tasks?page=1&limit=10
GET /api/v1/tasks?fields=id,name,slug
GET /api/v1/tasks?fields=status&distinct=true # Distinct values
GET /api/v1/tasks?ids=id1,id2,id3
GET /api/v1/tasks?ids=id1&ids=id2 # Alternative syntax
GET /api/v1/tasks?status=published
GET /api/v1/tasks?status=draft,published # OR logic
GET /api/v1/tasks?status=draft&priority=high # AND logic
GET /api/v1/tasks?search=keyword # Searches: name, title, slug, content
GET /api/v1/tasks?dateField=createdAt&from=2025-01-01&to=2025-12-31
GET /api/v1/tasks?taxonomyId=tax-123&taxonomyType=category
GET /api/v1/tasks?categoryId=tax-123 # Legacy convenience
GET /api/v1/tasks?metas=all # Include all metadata
GET /api/v1/tasks?metas=key1,key2,key3 # Specific keys only
GET /api/v1/clients/123?child=all # Include all children
GET /api/v1/clients/123?child=audiences,products # Specific types
GET /api/v1/tasks?sortBy=createdAt&sortOrder=DESC
GET /api/v1/tasks?sortBy=name&sortOrder=ASC
{
success: true,
data: [
{ id: "1", name: "Task 1", status: "active", createdAt: "2025-01-01T..." },
{ id: "2", name: "Task 2", status: "done", createdAt: "2025-01-02T..." }
],
info: {
timestamp: "2025-12-30T...",
total: 42,
page: 1,
limit: 10,
totalPages: 5,
hasNextPage: true,
hasPrevPage: false
}
}
{
success: true,
data: {
id: "1",
name: "Task 1",
status: "active",
createdAt: "2025-01-01T...",
updatedAt: "2025-01-01T..."
},
info: {
timestamp: "2025-12-30T..."
}
}
{
success: true,
data: {
id: "newly-created-id",
name: "New Task",
// ... all fields
},
info: {
timestamp: "2025-12-30T..."
}
}
{
success: false,
error: "Entity not found",
code: "NOT_FOUND",
details: { entityType: "tasks", id: "invalid-id" },
info: {
timestamp: "2025-12-30T..."
}
}
{
success: true,
data: {
id: "1",
name: "Task 1",
metas: {
seo_title: "Custom Title",
seo_description: "Meta description",
custom_key: { nested: "value" }
}
},
info: { ... }
}
{
success: true,
data: {
id: "client-123",
name: "Acme Corp",
child: {
audiences: [
{ id: "aud-1", name: "Enterprise", parentId: "client-123" },
{ id: "aud-2", name: "SMB", parentId: "client-123" }
],
products: [
{ id: "prod-1", name: "Product A", parentId: "client-123" }
]
}
},
info: { ... }
}
GET /api/v1/clients/{parentId}/child/audiences
GET /api/v1/clients/{parentId}/child/audiences?page=1&limit=20
POST /api/v1/clients/{parentId}/child/audiences
Content-Type: application/json
{
"name": "New Audience",
"description": "Target audience description",
"status": "active"
}
Response includes parentId:
{
success: true,
data: {
id: "aud-xyz",
parentId: "client-123", // Automatically set
name: "New Audience",
// ...
}
}
PATCH /api/v1/clients/{parentId}/child/audiences/{childId}
Content-Type: application/json
{
"name": "Updated Name"
}
DELETE /api/v1/clients/{parentId}/child/audiences/{childId}
// Include all metas
const response = await fetch('/api/v1/tasks/123?metas=all')
// Include specific metas
const response = await fetch('/api/v1/tasks/123?metas=seo_title,color_label')
Include metas in request body:
// Create with metas
await fetch('/api/v1/tasks', {
method: 'POST',
body: JSON.stringify({
name: 'Task Name',
status: 'active',
metas: {
seo_title: 'Custom SEO Title',
custom_field: { nested: 'value' }
}
})
})
// Update metas (merge behavior)
await fetch('/api/v1/tasks/123', {
method: 'PATCH',
body: JSON.stringify({
metas: {
seo_title: 'Updated Title' // Other metas preserved
}
})
})
Metadata Behavior:
// Team context (REQUIRED for team entities)
headers: {
'x-team-id': 'team-uuid'
}
// API key auth (optional)
headers: {
'Authorization': 'Bearer sk_...',
// OR
'x-api-key': 'sk_...'
}
// Builder source (enables blocks field)
headers: {
'x-builder-source': 'true'
}
| Operation | Required Scope |
|-----------|---------------|
| GET (list/read) | {entity}:read |
| POST (create) | {entity}:write |
| PATCH (update) | {entity}:write |
| DELETE | {entity}:delete or {entity}:write |
| Code | HTTP Status | Description |
|------|-------------|-------------|
| AUTHENTICATION_REQUIRED | 401 | No auth credentials |
| INVALID_API_KEY | 401 | API key invalid/expired |
| TEAM_CONTEXT_REQUIRED | 400 | Missing x-team-id header |
| INSUFFICIENT_PERMISSIONS | 403 | User lacks required scope |
| NOT_FOUND | 404 | Entity doesn't exist |
| VALIDATION_ERROR | 400 | Request body validation failed |
| CONFLICT | 409 | Duplicate or constraint violation |
import { useQuery } from '@tanstack/react-query'
function useTaskList(filters: TaskFilters) {
return useQuery({
queryKey: ['entity', 'tasks', filters],
queryFn: async () => {
const params = new URLSearchParams({
page: String(filters.page),
limit: String(filters.limit),
...(filters.status && { status: filters.status }),
...(filters.search && { search: filters.search }),
})
const response = await fetch(`/api/v1/tasks?${params}`)
if (!response.ok) throw new Error('Failed to fetch')
return response.json()
},
})
}
import { useMutation, useQueryClient } from '@tanstack/react-query'
function useCreateTask() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (data: CreateTaskData) => {
const response = await fetch('/api/v1/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) throw new Error('Failed to create')
return response.json()
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['entity', 'tasks'] })
},
})
}
// NEVER: Hardcode entity names in URLs
const endpoint = '/api/v1/tasks'
// CORRECT: Use entity config
const endpoint = `/api/v1/${entityConfig.slug}`
// NEVER: Skip team context for team entities
fetch('/api/v1/tasks') // Missing x-team-id!
// CORRECT: Always include team context
fetch('/api/v1/tasks', {
headers: { 'x-team-id': teamId }
})
// NEVER: Assume all entities have same fields
if (entity.status === 'active') { ... } // Not all entities have status!
// CORRECT: Check entity config for available fields
const hasStatus = entityConfig.fields.some(f => f.name === 'status')
// NEVER: Ignore pagination info
const allTasks = response.data // Could be truncated!
// CORRECT: Handle pagination
const { data, info } = response
if (info.hasNextPage) {
// Load more or show pagination
}
Before finalizing entity API integration:
x-team-id header for team entitiesinfo.hasNextPage, info.totalPages)success field in responsemetas parameter only when metadata neededentity-system - Entity definition (config, fields, types)tanstack-query - Data fetching patternsbetter-auth - Authentication patternsdevelopment
Zod validation patterns for this Next.js application. Covers schema definition, API validation, form integration, error formatting, and type inference. Use this skill when implementing validation for APIs, forms, or entity schemas.
development
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
testing
Test coverage metrics and registry system for this Next.js application. Covers FEATURE_REGISTRY, FLOW_REGISTRY, TAGS_REGISTRY, and coverage metrics interpretation. Use this skill when evaluating test coverage, identifying gaps, or planning testing priorities.
development
TanStack Query (React Query) patterns for data fetching in this Next.js application. Covers useQuery, useMutation, optimistic updates, cache invalidation, and anti-patterns. Use this skill when implementing data fetching or state management with server data.