Remove a custom, non-CRUD HTTP endpoint - two genuinely different shapes depending on the controller. A Public API endpoint's removal is just the controller action, its DTOs, and possibly a now-unused Api Client injection. An internal-service endpoint's removal is the controller action plus the paired Api Client custom request/method that called it, removed together since they're one contract. Use when the user asks to remove, delete, or drop a one-off custom action/endpoint from a Nano API o...
Installs into .claude/skills of the current project.
Are you the author of Nano Remove Custom Endpoint?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/nano-core-nano-remove-custom-endpoint-nano-templates)
---
name: nano-remove-custom-endpoint
description: Remove a custom, non-CRUD HTTP endpoint - two genuinely different shapes depending on the controller. A Public API endpoint's removal is just the controller action, its DTOs, and possibly a now-unused Api Client injection. An internal-service endpoint's removal is the controller action plus the paired Api Client custom request/method that called it, removed together since they're one contract. Use when the user asks to remove, delete, or drop a one-off custom action/endpoint from a Nano API or Web application.
---
# Nano remove custom endpoint
Removes a single custom HTTP action generated by `nano-add-custom-endpoint`. Read that skill
first; this one undoes exactly what it adds, following the same Public-API/internal-service split
and the same "Public API," not "gateway," terminology (see that skill's terminology note).
## Before removing anything, determine
1. **Which action, on which controller?** Confirm the exact method name and controller — "remove
the endpoint" alone isn't enough if the controller has several custom actions.
2. **Public API or internal-service controller?** Same distinction the scaffold skill makes —
check step 2 below for which path applies before doing anything else.
3. **Endpoint shape / naming** — confirm you have the actual file locations (which project each
piece lives in) rather than assuming the default convention was followed.
4. **Is this actually a redundant custom endpoint, not just an unused one?** Four distinct kinds
of "redundant," each worth naming explicitly rather than just deleting:
- The response is now expressible via generic `.Entity` + `[Include]` — see **Converting to
generic + Include** below instead of removing it outright.
- A sibling custom endpoint already returns everything this one does (e.g. a single-item lookup
that duplicates a list endpoint's already-populated shape — see
`nano-add-custom-endpoint`'s "check whether a sibling list endpoint already makes it
redundant" note). This is a plain removal, not a migration, but say plainly in the summary
which sibling endpoint now covers the removed one's job, so callers know where to go instead.
- The custom action's whole job was "the generic write plus an invariant" — see **Converting to
a generic-action override** below instead of removing it outright.
- Its Response DTO is now a near-duplicate of a sibling DTO because something it existed to
route around (an indirection layer, a since-removed entity) went away — see
`nano-add-custom-endpoint`'s "check for an existing sibling DTO" note. Removing the action and
switching remaining callers to the sibling DTO is a plain removal too, worth naming the same
way.
5. **Is a step in this endpoint's logic redundant because of DB-level cascade, not because the
endpoint itself is unneeded?** Before removing or "simplifying" a composed delete/update flow,
check whether an explicit child delete/cleanup call is actually doing something a required EF
Core relationship's default `Cascade` behavior already does for free (see
`nano-add-custom-endpoint`'s cascade note in step 1) — if so, that specific call is what's
redundant, not necessarily the endpoint around it. Confirm the parent isn't soft-deletable
(`IEntitySoftDeletable`) first — cascade doesn't apply to a soft delete, since it's an `UPDATE`,
not a real `DELETE`.
---
## Public API path
1. **Is the injected Api Client still needed by another action on this controller?** Check every
other action before deciding whether to also drop the constructor parameter/field — don't
remove a dependency other actions still use, and don't leave an unused-parameter compiler
error (`CS9113` under this solution's `TreatWarningsAsErrors`) behind either.
2. **Are the Request/Response DTOs used anywhere else?** Check for other actions in the same
project referencing the same `<Name>Request`/`<Name>Response` classes before deleting them.
### Files/code to remove
- The controller action itself (method, its `[Http*]`/`[Route]`/`[ProducesResponseType]`
attributes, its doc comment).
- `Requests/<Feature>/<Name>Request.cs` and `Responses/<Feature>/<Name>Response.cs` — only if
nothing else uses them.
- If this was the controller's only custom action and it has no generic CRUD/entity backing (a
bare `BaseController` created solely for this one endpoint), ask the user whether the now-empty
controller should go too — an empty controller isn't broken, just dead weight, so this is a
judgment call, not a mandatory cleanup step.
### Api Client injection
If nothing else on this controller uses the injected Api Client, remove the constructor
parameter/field. Don't remove the client's own definition or its `App:Apis` config entry as part
of this — that's `nano-remove-api-client-configuration`'s job (this app's consumption) or
`nano-remove-api-client`'s (the owning service's definition), separate decisions the user
should make explicitly rather than have cascade from removing one endpoint.
---
## Internal service path
This action **is** one half of a contract another application may call — its removal has to
account for the *paired* Api Client custom request/method the same way `nano-add-custom-endpoint`
scaffolds them together, not just the controller side.
1. **Is this action's route what another application's Api Client request targets?** This is the
real risk here: removing it breaks that caller. This skill has no visibility into other repos'
consumers — say so plainly rather than implying nothing calls it.
2. **Was a route constant shared** between this controller's `[Route(...)]` and the paired Api
Client request's action attribute (per the scaffold skill's route-constant-sharing step)?
Removing that constant is the sharpest version of the risk above — a guaranteed compile break
for the request class that referenced it, not just a stale endpoint. Confirm before removing.
3. **Is the shared body model used anywhere else?** Since the scaffold skill defines one model
class used by both the controller's `[FromBody]` parameter and the Api Client request's
`[Body]` property, check nothing else references it before deleting it.
### Files/code to remove
- The controller action itself.
- The paired Api Client method on this app's own client class (`{ThisApp}.Models/Api/{ClientName}.cs`).
- The Api Client request class (`{ThisApp}.Models/Api/Requests/{Name}Request.cs`).
- The shared body model and any dedicated response POCO — only if step 3 found nothing else uses
them.
- The route constant in `{Name}.Models/Consts/` — only if step 2 found it existed solely for this
action.
Remove all of these together, in the same change — this mirrors how they were scaffolded
together; leaving the Api Client method in place while deleting the controller action produces a
client that compiles but 404s the moment anyone calls it, which is worse than removing neither.
If the user's actual request was narrower — "just remove the Api Client method, keep the
controller action" or vice versa — that's a real but unusual ask; confirm that's really what they
want before doing a partial removal, since it deliberately leaves one side of the contract
without the other.
---
## Converting to generic + Include (instead of removing outright)
Sometimes the reason to remove a custom endpoint isn't that it's unused — it's that
`nano-add-custom-endpoint`'s step 1 decision procedure (built-in before custom) now says it never
needed to be custom in the first place: the same response is expressible as the target entity plus
`[Include]`-tagged navigations at some `includeDepth`. Handle this as one combined migration, not a
removal followed by an unrelated addition:
1. **Confirm the shape actually fits.** Walk through `nano-add-custom-endpoint`'s step 1 limits —
no selective `$expand`, and `[Include]` being global rather than per-caller — before assuming
this conversion applies. If either limit bites, this endpoint should stay custom; don't force
the conversion just because the response happens to include some navigations.
2. **Tagging `[Include]` on the entity changes its existing generic surface, not just this one
caller's response.** Every other consumer of that entity's generic endpoints — other internal
services, other Public APIs — becomes able to (and, depending on their own `includeDepth`,
will) eager-load this navigation too, once it's tagged. Say this plainly and confirm with the
user before tagging it, the same way `nano-remove-data-provider` confirms before deleting
mappings other code depends on.
3. **Update the caller to use the generic `.Entity` call directly**, with the right `includeDepth`
for what it actually needs (`0` for a bare entity, higher to reach the newly-tagged
navigation(s)) — see `nano-add-custom-endpoint`'s "don't wrap plain generic calls" note in its
Public API path; this replacement call should not go through a new custom Api Client method
either.
4. **Then remove the custom endpoint's pieces** per the Public-API/Internal-service steps above,
same as any other removal.
Report this to the user as one combined change: what got tagged `[Include]` and why, which other
consumers now gain visibility into it as a result, and what was removed because the generic call
now covers it.
---
## Converting to a generic-action override (instead of removing outright)
Sometimes a custom endpoint's whole job was never "a distinct operation" — it was "the generic
write, plus an invariant that must always hold" (validation before create/edit, a linked-entity
side-effect, a reference-count guard before a delete or a create). Per
`nano-add-custom-endpoint`'s "Overriding a generic CRUD action instead of a new endpoint," that
belongs as an override on the entity's own `BaseEntityController<...>` method(s), not a separate
route. Handle this as one combined migration:
1. **Confirm the endpoint doesn't need caller-context the override's fixed signature can't carry.**
An override of `EditAsync(TEntity entity, CancellationToken)`/`DeleteAsync(Guid id,
CancellationToken)` can't gain an extra parameter the removed custom action might have taken
(e.g. a `tenantId` used for ownership scoping). If the removed endpoint did real
caller-identity-based authorization — not just validation derivable from the entity/data itself
— that check has to move to (or stay at) the calling Public API as its own pre-check (e.g. a
scoped `QueryFirst` before calling the generic write), not disappear. Say this plainly rather
than silently dropping the check.
2. **Identify every generic write variant the invariant must survive.** If callers can reach
`CreateAsync`/`CreateAndGetAsync`/`CreateOrGetAsync`, or `EditAsync`/`EditAndGetAsync`, or
`DeleteAsync`/`DeleteManyAsync` — override each variant that's actually reachable, not just the
one the endpoint being removed happened to mirror. Missing a sibling variant reopens exactly the
gap the custom endpoint existed to close. A guard derived from another entity's existence (e.g.
"immutable once referenced") typically belongs on both the create and the delete variants, not
just whichever one the removed endpoint originally covered.
3. **Move the logic, adjusting for the override's constraints**: throw
`BadRequestException`/`NotFoundException` rather than returning bare `IActionResult`s for
anything that must propagate correctly through an Api Client (see `nano-add-custom-endpoint`'s
note on this); use `entity.Id` directly in a create override rather than the base call's result,
since `BaseEntity`'s constructor already assigned it; reconcile any collection navigation via
explicit repository calls, since generic Edit still won't do it for you.
4. **Then remove the custom endpoint's pieces** per the Internal service path steps above, same as
any other removal — including the paired Api Client method/request the caller used, once callers
are updated to hit the generic route directly instead.
Report this to the user as one combined change: which generic action(s) now carry the invariant,
which caller-context check (if any) had to move to the Public API instead, and what was removed
because the override now covers it.
---
## After making the change
- Show the user every file/method touched or deleted, grouped by concern, and which path was
taken.
- **Public API path**: if the Api Client injection was left in place because another action still
needs it, say so rather than leaving it unexplained.
- **Internal service path**: restate the cross-application risk from step 1 — this skill can only
confirm nothing *local* still calls it, not that nothing anywhere does. If step 2 found a
shared route constant, restate explicitly that removing it is a guaranteed break for whatever
other application's request referenced it, if any.