Agent skill
entity-api
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.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/entity-api
SKILL.md
Entity API Skill
Patterns for consuming the dynamic entity API system.
Architecture Overview
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 to Use This Skill
- Consuming entity endpoints in frontend code
- Understanding API response formats
- Working with query parameters (filtering, pagination, search)
- Implementing child entity operations
- Working with metadata (metas) system
Entity Resolution Order
When a request hits /api/v1/{entity}:
- Extract slug -
/api/v1/products→products - Check core path - Skip if: users, api-keys, auth, system, health, internal, admin, debug
- Registry lookup -
entityRegistry.getBySlug('products') - Custom override check - Look for
app/api/v1/(contents)/products/route.ts - Return resolution -
{ 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 }
}
Query Parameters
Pagination
GET /api/v1/tasks?page=1&limit=10
Field Selection
GET /api/v1/tasks?fields=id,name,slug
GET /api/v1/tasks?fields=status&distinct=true # Distinct values
Multiple IDs
GET /api/v1/tasks?ids=id1,id2,id3
GET /api/v1/tasks?ids=id1&ids=id2 # Alternative syntax
Filtering (Field-Based)
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
Search
GET /api/v1/tasks?search=keyword # Searches: name, title, slug, content
Date Range
GET /api/v1/tasks?dateField=createdAt&from=2025-01-01&to=2025-12-31
Taxonomy Filtering
GET /api/v1/tasks?taxonomyId=tax-123&taxonomyType=category
GET /api/v1/tasks?categoryId=tax-123 # Legacy convenience
Metadata
GET /api/v1/tasks?metas=all # Include all metadata
GET /api/v1/tasks?metas=key1,key2,key3 # Specific keys only
Child Entities
GET /api/v1/clients/123?child=all # Include all children
GET /api/v1/clients/123?child=audiences,products # Specific types
Sorting
GET /api/v1/tasks?sortBy=createdAt&sortOrder=DESC
GET /api/v1/tasks?sortBy=name&sortOrder=ASC
Response Formats
List Success (200)
{
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
}
}
Single Entity Success (200)
{
success: true,
data: {
id: "1",
name: "Task 1",
status: "active",
createdAt: "2025-01-01T...",
updatedAt: "2025-01-01T..."
},
info: {
timestamp: "2025-12-30T..."
}
}
Create Success (201)
{
success: true,
data: {
id: "newly-created-id",
name: "New Task",
// ... all fields
},
info: {
timestamp: "2025-12-30T..."
}
}
Error Response
{
success: false,
error: "Entity not found",
code: "NOT_FOUND",
details: { entityType: "tasks", id: "invalid-id" },
info: {
timestamp: "2025-12-30T..."
}
}
With Metadata
{
success: true,
data: {
id: "1",
name: "Task 1",
metas: {
seo_title: "Custom Title",
seo_description: "Meta description",
custom_key: { nested: "value" }
}
},
info: { ... }
}
With Children
{
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: { ... }
}
Child Entities
List Child Entities
GET /api/v1/clients/{parentId}/child/audiences
GET /api/v1/clients/{parentId}/child/audiences?page=1&limit=20
Create Child Entity
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",
// ...
}
}
Update Child Entity
PATCH /api/v1/clients/{parentId}/child/audiences/{childId}
Content-Type: application/json
{
"name": "Updated Name"
}
Delete Child Entity
DELETE /api/v1/clients/{parentId}/child/audiences/{childId}
Metadata System
Reading Metadata
// 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')
Writing Metadata
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:
- Stored separately from entity data
- Security inherited from parent entity via RLS
- Objects are merged, primitive types are replaced
- Lazy loaded only when requested
Authentication
Required Headers
// 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'
}
Scope Requirements
| Operation | Required Scope |
|---|---|
| GET (list/read) | {entity}:read |
| POST (create) | {entity}:write |
| PATCH (update) | {entity}:write |
| DELETE | {entity}:delete or {entity}:write |
Error Codes
| 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 |
Frontend Integration
TanStack Query Example
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()
},
})
}
Mutation Example
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'] })
},
})
}
Anti-Patterns
// 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
}
Checklist
Before finalizing entity API integration:
- Include
x-team-idheader for team entities - Handle pagination (
info.hasNextPage,info.totalPages) - Use correct query parameters for filtering
- Check
successfield in response - Handle error responses with appropriate UI feedback
- Use
metasparameter only when metadata needed - Invalidate queries after mutations
- Use proper HTTP methods (GET, POST, PATCH, DELETE)
Related Skills
entity-system- Entity definition (config, fields, types)tanstack-query- Data fetching patternsbetter-auth- Authentication patterns
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?