timewarpengineering/timewarp-architecture/tw-web-api-contracts
Claude Code and agent skill by timewarpengineering/timewarp-architecture
- community
Checking that the SKILL.md file is still in the repository.
**TIMEWARP SKILL** — endpoint-centric Web.Contracts API contracts (Command, Query, ApiRoute, Operation, I*Details, Validator, serialization tests), one contract bound to HTTP, CLI, and MCP. Invoke before scaffolding or fixing contracts. WHEN: Add a CreateTodoItem command contract, Scaffold a GetRole query with ApiRoute and IRoleDetails for the edit form, Add a serialization round-trip test for my Command.
Install
npx vskill@latest install timewarpengineering/timewarp-architecture/tw-web-api-contractsSKILL.md
Web API Contracts
Endpoint-centric, JSON-over-HTTP contracts designed for Blazor front ends. Each endpoint owns its request/response types. Mutability signals purpose: immutable members are read-only display data; mutable members on I*Details interfaces bind in EditForm without a separate view model. Shared validation lives on interfaces and is composed into per-endpoint validators.
This pattern appears across TimeWarp-based solutions. Project names vary (web-contracts, Web.Contracts, Api.Contracts, …) but the contract shape is the same.
Why endpoint-centric, not entity-centric: each operation owns its request/response types so endpoints do not couple through a shared entity DTO (over-fetch, under-fetch, and cross-endpoint breakage). Share structure and validation through I*Details interfaces composed into per-endpoint validators — that is what lets Blazor bind EditForm to one shape without a parallel view model.
Why generate FastEndpoints on both hosts: route, verb, and auth live on the contract, so there are no hand-written MVC BaseEndpoint shims. Validation stays on the mediator's FluentValidationBehavior. Hosts set EnableApiEndpointGeneration plus an ApiEndpointContractAssemblies AssemblyName allow-list (TWE008).
Browser-protocol endpoints (the exception)
A hand-written EndpointWithoutRequest at features/<slice>/…-endpoint-server.cs is allowed when the HTTP response is not the contract's JSON: an auth challenge, a form POST answered with a redirect, or an antiforgery token fetch. The living cases are ChallengeEntraEndpoint, SignOutBrowserSessionEndpoint, and SignOutAntiforgeryTokenEndpoint.
- The path lives on a shared contracts class.
- Any session or state change dispatches the existing mediator handler. Do not write a second
implementation.
- The Design region says why the generator does not fit.
- These bodies share nothing but the route and
AllowAnonymous, so a generator is not worth it. - Validation stays on
FluentValidationBehavior. Do not adopt FastEndpoints' validator integration.
Ordinary JSON operations stay generated. The filename function endpoint is for this exception (tw-feature-placement).
Detection — find the pattern in the current repo
Activate when any signal matches:
| Signal | How to find it | |
|---|---|---|
| Contracts project | *.csproj named *contracts* (any casing); product contracts may live in a cohesive web/features/ tree (axis-1) or a project-local features//Features/ tree | |
| Contract file layout | **/features/**/<use-case>/*-contracts.cs (one folder per operation, contract beside its handler) or a shared *-contracts.cs at slice root for a file used by more than one operation (axis-1 grammar; also match legacy *.cs without the layer suffix). Search case-insensitively — repos use kebab features/ or Pascal Features/ | |
| Contract shell | public static partial class + nested Query/Command + [ApiRoute(...)] | |
| TimeWarp.Mediator return | IQuery<…> / ICommand<…> / IIdempotentCommand<…> over OneOf<Response, SharedProblemDetails> (bare IRequest<…> where the kind is unclear) | |
| Neutral operation | [Operation("…")] (TimeWarp.Contracts) on the nested Query/Command | |
| Shared validation | I*Details interface + AbstractValidator<I*Details> |
The mediator is TimeWarp.Mediator (not MediatR). IQuery<T>, ICommand<T>, and IIdempotentCommand<T> all extend IRequest<T>, so handlers stay IRequestHandler<TRequest, TResponse>.
Before adding a contract, read 2–3 existing contracts in the same repo to match namespace root, folder casing, test project layout, and mock-service registration.
Folder and namespace rules
| Concern | Rule | Example | |
|---|---|---|---|
| Feature folder | Plural, domain-oriented; axis-1 cohesive root is web/features/<slice>/ | web/features/admin/roles/ | |
| Filename grammar | <name>[-<function>]-<layer>.cs; contracts drop function | create-role/create-role-contracts.cs | |
| Namespace | Plural (does not track folder moves) | {Root}.Features.Admin.Roles | |
| Use-case folder | One folder per operation, contract beside its handler (see tw-feature-placement) | create-role/create-role-contracts.cs, get-role/get-role-contracts.cs | |
| Shared bindable shape | Separate file at slice root, not inside any use-case folder | role-details-contracts.cs (IRoleDetails) | |
| Registry | Function→layer SSOT (analyzer + membership guard) | feature-filename-grammar.json — edit ⇒ full rebuild |
Casing: kebab-case paths are canonical; if the repo already uses PascalCase folders (Features/Admin/Roles/), match it. Never mix casings within one repo.
The contract attributes (source-generated)
Two layers of attributes. Route/request attributes are emitted as public types in TimeWarp.Foundation.Features by the bundled contracts generator (class must be `partial`). Server-generation attributes live in TimeWarp.Architecture.Attributes and mark which contracts become hosted FastEndpoints.
Route / request attributes (on nested Query/Command)
| Attribute | Generates | Use when | |
|---|---|---|---|
[ApiRoute("api/…", HttpVerb.X)] | RouteTemplate const, GetRoute(), GetHttpVerb(), and a typed property per route parameter ({RoleId:guid} → Guid RoleId) | Every contract request | |
[AuthApiRequest] | Guid UserId { get; set; }; private GetAuthQueryParameters() only when the same type is IQueryStringRouteProvider or also has [OpenDataQueryParameters] | List/GET queries that carry user identity in the query string | |
[OpenDataQueryParameters] | Top/Skip/Filter/OrderBy/ReturnTotalCount + private GetOpenDataQueryParameters() | Pageable/sortable list queries |
The FastEndpoint generator matches TimeWarp.Foundation.Features.ApiRouteAttribute by fully qualified metadata name; the contracts generator emits that public type, so it ignores the generated app's RootNamespace.
[ApiRoute] parameter constraint grammar
Tokens are {Name} or {Name:constraint}; a type starts only after a colon, so bare {Date}/{LocationId} stay full identifiers typed string. guid → Guid, datetime → DateTime, min(n)/max(n) → int, other tokens as written. Full constraint table and unsupported forms: route-grammar.md.
FastEndpoint generation (on the outer operation class)
Both web-server and api-server host endpoints generated from contracts — there are no hand-written MVC BaseEndpoint shims in the template. Opt in per operation:
| Attribute | Effect | Use when | ||
|---|---|---|---|---|
[ApiEndpoint] | Generator emits `BaseFastEndpoint<Op.Query\ | Command, Response>` for this operation | Every contract hosted on a server with EnableApiEndpointGeneration | |
[EndpointAuthorize(Policy=…, AuthenticationSchemes=…)] | Generator emits Policies("…") / Roles(…) / AuthSchemes(…) | Protected routes. Always set `AuthenticationSchemes` on hosted contracts — see scheme lists below | ||
[EndpointAllowAnonymous(reason)] | Generator emits AllowAnonymous() | Genuinely public / pre-auth ceremony endpoints — reason is a required, honest, per-contract string | ||
| (no marker) | Fail-closed (task 110): generator emits nothing, so FastEndpoints' own default (auth required) applies | Never — every [ApiEndpoint] contract must carry exactly one of the two markers above; TWA0013 flags the omission at build time |
Picking anonymous when the contract shouldn't be is caught too: TWA0014 flags a contract that carries both markers, or [EndpointAllowAnonymous] alongside a nested Query/Command that declares IAuthApiRequest (manually or via [AuthApiRequest]) — see the auth-forms section below for why that combination is a contradiction, not just a style nit.
Policy names (TWA0024): [EndpointAuthorize] Policy must equal a policy the hosting server registers (web: PermissionIds.*; named policies via AddPolicy). Prefer a contracts-visible const over a literal.
Scheme lists: hosted [EndpointAuthorize] must set AuthenticationSchemes (AuthenticationSchemeNames on web); permission policies carry no scheme list, so omitting it authenticates only the host default scheme. Details and per-surface table: endpoint-auth.md.
Validation stays on the mediator (FluentValidationBehavior). Do not re-validate in handlers and do not wire FastEndpoints' own FluentValidation integration (IncludeAbstractValidators = false). Handlers implement business logic only; the generated endpoint is pure HTTP plumbing.
Server projects set <EnableApiEndpointGeneration>true</EnableApiEndpointGeneration> and must set ApiEndpointContractAssemblies to the hosted contracts AssemblyName (web-contracts on web-server, api-contracts on api-server — not the namespace). Empty, mistyped, or unmarked names are TWE008. Hosted contracts assemblies apply [assembly: ApiEndpointsEmbedded]; host generators walk only marked refs.
HTTP verbs
| Operation | Verb | Usual kind | |
|---|---|---|---|
| Query | Get | IQuery<…> | |
| Create | Post | ICommand<…> | |
| Update (full replace) | Put | IIdempotentCommand<…> | |
| Delete | Delete | IIdempotentCommand<…> |
The verb is the contract's [ApiRoute] verb. Generated FastEndpoints take it from there. Do not hand-write a second verb. Analyzer id TWA0005 (MVC verb vs [ApiRoute]) is retired and reserved.
The kind does not pick the verb, and the verb does not pick the kind. The column above is the usual pairing, not a rule: IIdempotentCommand promises that a retry is safe, so use ICommand when the handler cannot keep that promise. See neutral-operations.md.
Neutral operations: one contract, three bindings
The TimeWarp.Contracts package adds the transport-neutral part of a contract. Its version pin is its own line in Directory.Packages.props. The same request class is an HTTP endpoint, a CLI command, and an MCP tool:
- Name, group, and title:
[Operation("roles.get", Description = "…", Group = "…", Title = "…")]
on the nested Query/Command. A blank title means no title. [Destructive] and [OpenWorld] are tri-state: omitted is unset, not false. MCP treats an omitted destructive hint as true and reads it only when the tool is not read-only. Generators copy the hints only when [Operation] has a non-blank name. Kind still supplies read-only and idempotent.
- Kind: the mediator interface (
IQuery<T>,ICommand<T>,IIdempotentCommand<T>). It is
never an attribute argument.
- Bindings: HTTP comes from
[ApiRoute]+[ApiEndpoint](this template). CLI and MCP come
from TimeWarp.Nuru, which reads the same contract and dispatches through the mediator.
TWA0035 (info) suggests [Operation] on a hosted contract that lacks one. Members, kind choice, bindings, and the catalog: neutral-operations.md.
Contract shell
Every operation is a public static partial class named for the operation:
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/tw-web-api-contracts/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/timewarpengineering/timewarp-architecture/tw-web-api-contracts)