en un clic
api-contract
>-
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Menu
>-
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Basé sur la classification professionnelle SOC
Route complex requests to the right specialist agent or chain of agents. This skill acts as the central brain of an agent swarm — it analyses what the user needs, determines which specialist domain(s) are required, and coordinates parallel or sequential agent execution. Use this skill when a request spans multiple domains (e.g., "research competitors and create a pitch deck"), when you need to decide which specialist should handle an ambiguous request, or when a task requires a multi-step pipeline across different skills. Triggers on: multi-step requests, cross-domain tasks, "coordinate", "plan this out", "I need help with multiple things", or any complex request that touches more than one specialist area. Also triggers when the user seems unsure which tool or approach to use.
Hybrid DAG execution primitive combining deterministic and agentic nodes with hard iteration caps
">"
Act as a brand ambassador — create authentic, platform-specific social media content that embodies a brand's identity and connects with audiences. Use this skill whenever the user asks to "create social media content for a brand", "act as a brand ambassador", "write ambassador posts", "promote [brand] on social media", "create influencer-style content", "write authentic brand content", "social media ambassador", or any request involving representing a brand through social content. Also triggers on "ambassador voice", "brand promotion posts", "influencer content", "authentic brand posts", "UGC-style content", or when someone wants social media content that sounds like a real person recommending a brand rather than corporate marketing copy. Even if the user just says "help me promote [brand]" or "I need content for [brand]'s social channels" — use this skill.
>-
Override generic LLM code output patterns. Replaces generic variable names, over-commented code, and placeholder patterns with domain-specific naming, clean architecture, and production-ready structure.
| id | api-contract |
| name | api-contract |
| type | skill |
| version | 1.0.0 |
| created | 20/03/2026 |
| modified | 20/03/2026 |
| status | active |
| metadata | {"author":"NodeJS-Starter-V1","version":"1.0.0","locale":"en-AU"} |
| description | >- |
| context | fork |
Ensures every API endpoint has a typed contract: Pydantic models on the backend, Zod response schemas on the frontend, and documented error responses. Bridges the gap between apps/backend/src/api/ and apps/web/lib/api/.
Enforces type-safe API contracts across the FastAPI backend and Next.js frontend. Every endpoint must declare a Pydantic response_model, have a matching Zod schema on the frontend, and document error responses via OpenAPI. Prevents schema drift, untyped responses, and manual type definitions by mandating a single source of truth through the Contract Triangle pattern.
apiClient.get/post/put/patch/delete)data-validation instead)error-taxonomy instead)retry-strategy when available)Backend (Pydantic) ←── Contract ──→ Frontend (Zod)
↓ ↓
response_model z.infer<typeof schema>
↓ ↓
OpenAPI spec Type-safe apiClient
Every endpoint must have:
response_model on the route decoratorresponses={} dictUse snake_case consistently across both sides:
# Backend (Pydantic)
class DocumentItem(BaseModel):
created_at: datetime
error_code: str
// Frontend (Zod) — mirror the snake_case
const documentItemSchema = z.object({
created_at: z.string().datetime(),
error_code: z.string(),
});
Do NOT convert to camelCase on the frontend. The API contract is snake_case end-to-end.
The project already uses two patterns. Standardise to:
| Schema Scope | Location | When to Use |
|---|---|---|
| Route-specific | Inline in route file | Used by 1 endpoint only (e.g., DocumentCreateRequest) |
| Shared across routes | apps/backend/src/api/schemas/{domain}.py | Used by 2+ endpoints or referenced by frontend |
| Domain models | apps/backend/src/models/ | Business logic models, not request/response shapes |
Existing examples:
apps/backend/src/api/routes/documents.py (DocumentItem, DocumentListResponse)apps/backend/src/api/schemas/workflow_builder.py (WorkflowNodeResponse, etc.)apps/backend/src/models/contractor.py (Contractor, Location, ErrorResponse)Every route MUST declare response_model:
# GOOD: Explicit response model
@router.get(
"",
response_model=DocumentListResponse,
summary="List documents",
responses={
401: {"model": ErrorResponse, "description": "Unauthorised"},
500: {"model": ErrorResponse, "description": "Internal error"},
},
)
async def list_documents(...) -> DocumentListResponse:
...
# BAD: Untyped dict response
@router.get("/data")
async def get_data() -> dict:
return {"stuff": "things"}
Use consistent wrapper patterns for list endpoints:
from pydantic import BaseModel, Field
class PaginationInfo(BaseModel):
"""Reusable pagination metadata."""
total: int = Field(description="Total matching items")
limit: int = Field(description="Results per page")
offset: int = Field(description="Current offset")
pages: int = Field(description="Total number of pages")
class PaginatedResponse(BaseModel):
"""Generic paginated response base. Subclass with typed `data` field."""
pagination: PaginationInfo
Subclass for each domain:
class DocumentListResponse(BaseModel):
data: list[DocumentItem]
pagination: PaginationInfo
Every route should document error responses using the ErrorResponse model from error-taxonomy:
from src.models.contractor import ErrorResponse
@router.post(
"",
response_model=DocumentItem,
status_code=201,
responses={
401: {"model": ErrorResponse, "description": "Unauthorised"},
422: {"model": ErrorResponse, "description": "Validation error"},
409: {"model": ErrorResponse, "description": "Duplicate resource"},
},
)
Ensure every route has:
@router.get(
"/path",
response_model=ResponseType, # Required
summary="Short description", # Required (appears in sidebar)
description="Detailed description", # Recommended
tags=["Domain"], # Required (groups in docs)
responses={...}, # Required (error documentation)
)
The FastAPI app already generates OpenAPI at /docs (Swagger) and /redoc.
Create Zod schemas that mirror backend Pydantic models:
// apps/web/lib/api/schemas/documents.ts
import * as z from 'zod';
// Mirror of backend DocumentItem
const documentItemSchema = z.object({
id: z.string(),
title: z.string(),
content: z.string().nullable(),
metadata: z.record(z.unknown()),
created_at: z.string(),
updated_at: z.string(),
});
// Mirror of backend PaginationInfo
const paginationSchema = z.object({
total: z.number(),
limit: z.number(),
offset: z.number(),
pages: z.number(),
});
// Mirror of backend DocumentListResponse
const documentListResponseSchema = z.object({
data: z.array(documentItemSchema),
pagination: paginationSchema,
});
// Infer types — never define manually
type DocumentItem = z.infer<typeof documentItemSchema>;
type DocumentListResponse = z.infer<typeof documentListResponseSchema>;
export {
documentItemSchema,
documentListResponseSchema,
type DocumentItem,
type DocumentListResponse,
};
apps/web/lib/api/
├── client.ts # Existing fetch wrapper
├── auth.ts # Existing auth API
└── schemas/ # NEW: Response schemas
├── common.ts # Pagination, ErrorResponse
├── documents.ts # Document schemas
├── workflows.ts # Workflow schemas
├── agents.ts # Agent/discovery schemas
└── contractors.ts # Contractor schemas
Wrap apiClient calls with Zod parsing for runtime safety:
import { apiClient } from '@/lib/api/client';
import {
documentListResponseSchema,
type DocumentListResponse,
} from '@/lib/api/schemas/documents';
export async function listDocuments(
params?: { limit?: number; offset?: number }
): Promise<DocumentListResponse> {
const query = new URLSearchParams();
if (params?.limit) query.set('limit', String(params.limit));
if (params?.offset) query.set('offset', String(params.offset));
const raw = await apiClient.get(`/api/documents?${query}`);
return documentListResponseSchema.parse(raw);
}
Mirror the backend ErrorResponse on the frontend:
// apps/web/lib/api/schemas/common.ts
import * as z from 'zod';
const errorResponseSchema = z.object({
detail: z.string(),
error_code: z.string().optional(),
severity: z.enum(['fatal', 'error', 'warning']).optional(),
field: z.string().optional(),
});
type ErrorResponse = z.infer<typeof errorResponseSchema>;
export { errorResponseSchema, type ErrorResponse };
When adding or modifying an API endpoint:
response_model declaredsummary and tagsresponses={} dictField(description=...) on all fieldsConfigDict(from_attributes=True) if mapping from ORMapps/web/lib/api/schemas/z.infer, not manually definedOptional (backend) and .nullable() (frontend)PaginationInfo patternWhen the API needs breaking changes:
# apps/backend/src/api/main.py
# Version 1 (current)
app.include_router(documents.router, prefix="/api/v1", tags=["Documents v1"])
# Version 2 (new)
app.include_router(documents_v2.router, prefix="/api/v2", tags=["Documents v2"])
Mark deprecated endpoints with headers:
from fastapi import Response
@router.get("/old-endpoint", deprecated=True)
async def old_endpoint(response: Response):
response.headers["Deprecation"] = "true"
response.headers["Sunset"] = "2026-06-01"
response.headers["Link"] = '</api/v2/new-endpoint>; rel="successor-version"'
return {"data": "still works"}
A change is breaking if it:
A change is non-breaking if it:
Grep for contract violations:
# Backend: Routes missing response_model
rg "def (get|post|put|patch|delete)_" apps/backend/src/api/routes/ | \
rg -v "response_model"
# Backend: Routes returning raw dict
rg "-> dict" apps/backend/src/api/routes/
# Frontend: Unvalidated API calls (no .parse())
rg "apiClient\.(get|post|put|patch|delete)" apps/web/ | \
rg -v "Schema\.parse"
| Pattern | Problem | Correct Approach |
|---|---|---|
Untyped API responses (-> dict) | No compile-time or runtime validation, silent breakage | Declare response_model on every route with a Pydantic model |
| Frontend/backend schema drift | Field mismatches cause runtime errors in production | Mirror Pydantic models with Zod schemas; validate with .parse() |
| No response envelope pattern | Inconsistent list responses, missing pagination metadata | Use PaginatedResponse wrapper with data and pagination fields |
| Manual type synchronisation | Types fall out of sync as endpoints evolve | Infer frontend types via z.infer<typeof schema>, never define manually |
| Missing error response documentation | Consumers cannot handle failures gracefully | Document all error codes in responses={} dict using ErrorResponse model |
response_model defined on every route decoratordata + pagination)summary, tags, and responses on all routesErrorResponse model.parse())[AGENT_ACTIVATED]: API Contract
[PHASE]: {Audit | Implementation | Review}
[STATUS]: {in_progress | complete}
{contract analysis or implementation guidance}
[NEXT_ACTION]: {what to do next}
Error responses use ErrorResponse from error-taxonomy. Every responses={} dict references the canonical error model.
data-validation handles request validation (Zod for forms, Pydantic for bodies). api-contract handles response validation (Zod schemas mirroring Pydantic response models).
Request: User → Zod (form) → fetch → Pydantic (body) → Service
Response: Service → Pydantic (response_model) → JSON → Zod (parse) → Component