Capabilities
Every Trellis template ships the same set of cross-cutting capabilities. You don't wire them up — they're already there, consistently, in both templates. This page explains what each one does and how you use it.
These capabilities are not aspirational. Each is asserted by the capability-parity contract, so a template that drops one fails CI.
API versioning
APIs are versioned by date (e.g. 2026-03-26). A new version is a new, additive thing — you never
silently break an existing client. In the ASP template each version is its own controller namespace; in the
microservices template each version is an endpoint group. Clients select a version per the configured
version reader, and each version gets its own OpenAPI document.
Service Level Indicators
Every operation emits an operation.duration metric tagged with the operation name, derived
automatically from the route (e.g. GET /api/members/{id}). That gives you per-endpoint latency and
availability with no manual instrumentation, ready to drive SLOs and dashboards.
Idempotency
Mutating endpoints honor an Idempotency-Key header: a retried request with the same key returns the
original result instead of performing the work twice. Network blips and client retries stop being a
data-integrity problem.
Problem Details
Every error is an RFC 9457 application/problem+json document — a stable, machine-readable shape with a
type, title, status, and detail. Validation and business-rule failures return 422 Unprocessable Content (per the RFC, 400 is reserved for syntactically malformed requests), so clients can react to
errors programmatically.
OpenAPI & Scalar
Each API version publishes an OpenAPI document at /openapi/{version}.json, and a modern
Scalar API reference UI to explore and try the API in the browser. The OpenAPI
document is generated from your code, so it never drifts from reality.
Observability
Services are instrumented with OpenTelemetry for traces, metrics, and logs. Crucially, the mediator pipeline's spans are exported and business events are logged structurally, all sharing the same trace and span ids — so a single request can be followed from the HTTP edge, across services, through each handler, in one trace.
Authorization
Authorization is actor-based and declared on the message. A command or query states the permissions it requires (and, for resource-based checks, how to load the resource), and the mediator pipeline enforces it before the handler runs. Authorization logic lives in one place, not scattered through handlers.
Scalar value validation
Domain ids and other scalars are value objects, not raw Guid/string. If a caller puts a malformed id
in the route, the binding fails cleanly into a 422 ProblemDetails — never an unhandled 500. Validity
is enforced at the type level, at the edge.
Mediator pipeline
Commands and queries flow through a mediator pipeline of behaviors — validation, authorization, tracing, and more — so handlers stay focused on domain logic. Cross-cutting concerns are pipeline steps, added once.
Health checks
Services expose health endpoints (/health and friends) for liveness and readiness, ready for
container orchestrators and load balancers to probe.