Apply this skill when adding an endpoint to apps/api/, authoring a mutation-ack serializer, wiring ManagerFactory, resolving Phase 5A routing collisions, or diagnosing the validate_manifest or test_resource_route_coverage CI gates. Covers: resource vs. action classification (409 vs. 200+envelope); Cursor + paginate() pagination in services/pagination.py; ManagerFactory concurrency scoping; apps/api → unifi-core-only dependency rule; Phase 5A patterns (capability-aware dispatch on product_kinds, dual-kind stats, wrapper-dict unwrap, path disambiguation, TOOL_ROUTE_OVERRIDES in test_resource_route_coverage.py); @register_serializer(tools={...}) decorator; seven RenderKind values; field-curation discipline; _reset_registry_for_tests() isolation; and the two CI gates. Post-Phase 6: read tools use Strawberry types (graphql-api-extension skill); serializers cover mutation acks only. Activate even without explicit serializer mention — any apps/api/ PR may need CI-gate alignment.
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.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
La commande reste sur une seule ligne. Faites défiler horizontalement pour la vérifier avant de la copier.
Vous préférez une copie locale ? Téléchargez les fichiers actuellement disponibles dans SkillsMP.
Affichage de SKILL.md
SKILL.md
Instructions source · Aperçu en lecture seule
name
myco:api-endpoint-serializer-authoring
managed_by
myco
display_name
API Endpoint and Serializer Authoring (apps/api)
user-invocable
false
description
Apply this skill when adding an endpoint to apps/api/, authoring a mutation-ack serializer, wiring ManagerFactory, resolving Phase 5A routing collisions, or diagnosing the validate_manifest or test_resource_route_coverage CI gates. Covers: resource vs. action classification (409 vs. 200+envelope); Cursor + paginate() pagination in services/pagination.py; ManagerFactory concurrency scoping; apps/api → unifi-core-only dependency rule; Phase 5A patterns (capability-aware dispatch on product_kinds, dual-kind stats, wrapper-dict unwrap, path disambiguation, TOOL_ROUTE_OVERRIDES in test_resource_route_coverage.py); @register_serializer(tools={...}) decorator; seven RenderKind values; field-curation discipline; _reset_registry_for_tests() isolation; and the two CI gates. Post-Phase 6: read tools use Strawberry types (graphql-api-extension skill); serializers cover mutation acks only. Activate even without explicit serializer mention — any apps/api/ PR may need CI-gate alignment.
Resource endpoint (GET /v1/sites/{id}/cameras): implies the resource
exists; return HTTP 409 when the required product is absent.
Action endpoint: advisory; always return HTTP 200 with a
capability_not_available envelope when the capability is missing — never
404 or 409 for action endpoints.
2. Wire cursor-based pagination
Use Cursor and paginate() from
apps/api/src/unifi_api/services/pagination.py:
from unifi_api.services.pagination import Cursor, InvalidCursor, paginate
cursor = Cursor.decode(cursor_str) if cursor_str elseNone
page, next_cursor = paginate(items, cursor=cursor, limit=limit, key_fn=key_fn)
Cursor encodes {last_id, last_ts} as Base64 — survives new inserts;
offset/page-number pagination does not.
Default limit 50, max 200. Expose cursor as an opaque query-string param.
3. Apply the dependency rule
apps/api/ MUST only import from unifi-core. Never import unifi-mcp-shared
inside apps/api/ — it couples the REST server to MCP protocol concerns.
4. Wire ManagerFactory
ManagerFactory lives in apps/api/src/unifi_api/services/managers.py. It
caches one manager per (controller_id, product) pair behind asyncio locks:
product_kinds in the DB row is a comma-separated string. The factory
splits it and raises UnknownProduct if the product isn't listed — so set
product_kinds accurately when registering a controller.
Each concurrency scope (request) shares the cached manager instance; no
per-request teardown.
B. Phase 5A Advanced Routing Patterns
Capability-aware dispatch
When two products expose identically-named endpoint families (e.g., both
Network and Protect expose /events), dispatch at request time by consulting
controller.product_kinds. Route to the correct manager based on which
products are active — avoids collisions without duplicate routes.
Path disambiguation for cross-product collisions
Add per-product prefixes on ambiguous routes:
/access/events — not /events
/protect/health — not /health
Without prefixes, three product packages collide in the route namespace.
Dual-kind stats
Accept a kind=timeseries|detail query parameter on the same endpoint; select
the appropriate RenderKind at the route layer. Avoids duplicating tools for
two render modes of the same data.
Wrapper-dict unwrap pattern
When a UniFi API returns a wrapper dict (e.g., switch-ports, port-stats,
lldp-neighbors):
Register the serializer as RenderKind.DETAIL.
At the route layer, call the tool inline and override render_hint to
emit LIST.
Do NOT push list logic into the DETAIL serializer.
TOOL_ROUTE_OVERRIDES
When a route function name doesn't follow the default convention (tool name
minus product prefix), add an entry to TOOL_ROUTE_OVERRIDES in:
apps/api/tests/test_resource_route_coverage.py
This is a test-time mapping only — it is NOT applied at server startup.
Adding an override here does NOT register a route; you still need a real
FastAPI route function. The dict currently has 17 entries (Task 22 audit).
C. Authoring a Serializer (Post-Phase 6)
Phase 6 shift: Read tools (unifi_list_*, unifi_get_*, protect_*,
access_list_*) now project via Strawberry types — see the
graphql-api-extension skill. The serializer layer covers mutation acks
only (create/update/delete/toggle). validate_manifest accepts either a
serializer or a Strawberry type registration as valid coverage.
Seven values in apps/api/src/unifi_api/serializers/_base.py:
Value
Use
LIST
Collection of items
DETAIL
Single resource or mutation ack
DIFF
Before/after comparison
TIMESERIES
Time-ordered data points
EVENT_LOG
Event stream
EMPTY
Action with no meaningful return
STREAM
Streaming response
4. Field curation discipline
Declare exactly which fields appear for each kind. Serializers are curated,
not pass-through. Do not add a "return everything" serializer — the cumulative
curation effort across 235+ tools is intentional.
5. Serializer layering rule
Serializers live in apps/api/ only. Managers and MCP tool functions MUST NOT
reference render hints or catalog metadata. This keeps MCP servers free of
serialization coupling (decision-a63cb266).
D. CI Gates
validate_manifest
Runs at server startup via discover_serializers() in
apps/api/src/unifi_api/serializers/_registry.py. Raises
SerializerRegistryError if any manifest tool lacks both a serializer
registration and a Strawberry type registration. After Phase 6, you need
at least one — not necessarily both.
Every read tool must have a registered GET resource route. When adding a read
tool:
Register a route function in the FastAPI app.
If the name doesn't follow the default convention, add to
TOOL_ROUTE_OVERRIDES in that test file.
Run pytest apps/api/tests/test_resource_route_coverage.py locally.
Test isolation for serializer tests
Import _reset_registry_for_tests from unifi_api.serializers._registry.
It clears module-level registries AND evicts serializer submodules from
sys.modules, forcing discover_serializers to re-import and re-run all
decorators. Call in both setup and teardown.
from unifi_api.serializers._registry import _reset_registry_for_tests
defsetup_function():
_reset_registry_for_tests()
defteardown_function():
_reset_registry_for_tests()
Cross-Cutting Gotchas
ManagerFactory, not ControllerFactory: class lives in
apps/api/src/unifi_api/services/managers.py. Wrong name → ImportError.
product_kinds is comma-separated in the DB: Always split with
.split(",") and filter empty strings. Inaccurate product_kinds causes
UnknownProduct at request time.
TOOL_ROUTE_OVERRIDES is test-only: Does not register routes. You still
need a real FastAPI route function registered in the app.
Live smoke required: Mock CI cannot detect UniFi API contract violations.
Six confirmed violation classes only surfaced via live hardware testing.
See live-smoke-testing skill before cutting a PR.
apps/api dependency boundary is enforced: Importing unifi_mcp_shared
anywhere in apps/api/ is a build-time error.