Developing APIs
APIs are the HTTP boundary of a Lino service. They expose application layer use cases without mixing transport concerns with domain rules. This chapter explains how Lino generates Minimal APIs, request/response contracts, typed clients, OpenAPI metadata and automatic registration of endpoints by source generators.
The generated endpoint must stay thin: receives HTTP data, assembles a command or query, sends it to the Application layer via ISender and converts the result to a typed response. Validation, business rules, persistence, transactions and domain authorization continue in commands, queries, handlers, validators, repositories and Unit of Work.
Minimal APIs on Lino
Lino adopts ASP.NET Core Minimal APIs as a standard for generating endpoints, because they keep the code direct, explicit and performant without breaking the boundaries of Clean Architecture.
In current Lino projects, generated endpoints are Minimal API classes that implement IEndpoint. Each endpoint exposes a static method MapEndpoint(IEndpointRouteBuilder app) and a handler method that uses ISender to dispatch the generated command or query to the Application layer.
Generated main parts
*Endpoint.cs: maps the HTTP method, route, metadata, authorization, tenant requirement, rate limiting and version.*Request.cs: represents input coming from body, query string, route parameters or form data.*Extensions.cs: converts request to command/query and converts the application result to an HTTP response.*Response.cs: defines the strongly typed contract returned by the endpoint and typed clients.
Built-in features
Lino uses TypedResults and Results<...> so that success and error responses are explicit in the code and appear correctly in OpenAPI. Faults coming from the Application layer are converted to ProblemDetails through result.MapToProblemDetails().
WithTags: Groups endpoints by module, entity, or feature in OpenAPI and Scalar.WithName: Sets a consistent operationId when applicable.WithSummary: clearly describes the purpose of the endpoint.Produces(statusCode, schema): specifies status codes and response contracts.MapToApiVersion(1, 0): associates the endpoint with the API version.RequirePermission,RequireAuthorization,AllowAnonymousandRequireTenant: apply security according to the project options.- Rate limiting at the group or endpoint level, using authenticated or anonymous policies depending on the configuration.
Requests and Responses
By default, Lino uses records for requests and responses. They are concise, immutable by default, and work well as explicit contracts between API, typed clients, and external consumers.
- Requests received via endpoint are transformed into Commands or Queries.
- Results of Commands or Queries are converted into Responses.
- Domain entities should not be directly exposed as an HTTP contract.
public record CreatePersonRequest(string Name, int Age); public record CreatePersonResponse(Guid Id, string Name, int Age);
Creating new APIs
Create APIs with the CLI when the domain model and use case are already clear enough to be exposed by HTTP. APIs connect commands and queries to the presentation layer via Minimal APIs, contracts, typed responses and documentation metadata.
lino api new lino api new --name <ApiName> --service <ServiceName> --module <ModuleName> --entity <EntityName> lino api list --service <ServiceName> --module <ModuleName> --entity <EntityName>
The interactive wizard will prompt:
- Service: service where the API will be created.
- Module: service module, when applicable.
- Entity or enumeration: domain element associated with the endpoint.
- API name: normally aligned with the verb of the operation, such as
CreatePerson. - Type of operation:
GET,POST,PUT,PATCH,DELETE, upload, download or options endpoint. - Route: route pattern, like
/people/{id:guid}. - Properties: request and response fields that must actually cross the HTTP boundary.
What the wizard can configure
- GET: single result, list, paged list and options/select scenarios.
- POST: creation and business actions, including command type and selected properties.
- PUT and PATCH: complete or partial update.
- DELETE: removal mapped to delete commands.
- Upload: endpoints with
IFormFileandDisableAntiforgery()when necessary. - Download:returning endpoints
FileStreamHttpResult; authenticated projects can generate a token endpoint for secure file access. - Enumerations: endpoints that expose valid options per generated query and response.
Recommended flow
- Create or review the entity, commands, and queries that represent the use case.
- Run
lino api newand select correct service, module and entity. - Choose the type of operation by use case, not just by the desired HTTP verb.
- Define stable route, using constraints like
{id:int}or{id:guid}when appropriate. - Select only properties that must cross the HTTP boundary.
- Define authorization, permission, tenant requirement, rate limiting and expected status codes.
- Run the build and inspect Scalar/OpenAPI to confirm route, summary, version, security and responses produced.
Example: CreatePerson
When creating a API POST call CreatePerson, associated with the entity Person, CLI generates endpoint, request/response contracts, mapping extensions, OpenAPI metadata and integration with the corresponding command.
<ProjectName>/
βββ src/
βββ Services/
βββ <ServiceName>/
βββ Api/
βββ Endpoints/
βββ People/
βββ CreatePerson/
βββ CreatePersonEndpoint.cs
βββ CreatePersonExtensions.cs
βββ CreatePersonRequest.cs
βββ CreatePersonResponse.cs
Contracts and typed clients
When the project has a Web App Blazor, Lino also generates artifacts for typed consumption of APIs: shared contracts, client interface and HTTP implementation. This allows Blazor to consume endpoints in a simple, consistent, and strongly typed way.
<ProjectName>/
βββ src/
βββ Services/
βββ <ServiceName>/
βββ Api.Contracts/
β βββ Features/
β βββ People/
β βββ CreatePerson/
β β βββ CreatePersonRequest.cs
β β βββ CreatePersonResponse.cs
β βββ IPersonApiClient.cs
βββ Api.Client/
βββ Features/
βββ PersonApiClient.cs
The client interface is registered for dependency injection, and the implementation uses HttpClientProvider with Tolitech's HTTP helpers to call the generated API in a strongly typed way.
Checklist before publishing
- Use
GETfor reading,POSTfor creation/actions,PUT/PATCHfor change andDELETEfor removal when it makes sense. - Do not expose domain entities directly as an external contract.
- Document validation, conflict, not found, and access denied errors.
- Use
api listto avoid duplicate routes or competing endpoints for the same use case.
Endpoint Registration with Source Generators
Lino avoids manually mapping large files using source generators. Instead of listing endpoints one by one in Program.cs, registration is produced during compilation.
Generated endpoint classes implement IEndpoint from Tolitech.MinimalApis.Generators.Abstractions. The endpoint group calls MapEndpointsGenerated(), produced by Tolitech.MinimalApis.Generators, and new endpoints are registered by generated code.
Why this matters
- Less manual wiring: Developers don't need to remember to map each endpoint manually.
- Compile-time consistency: endpoints follow the same structure and are discovered by generated code.
- Cleaner startup:
Program.csdelegates setup for configuring services, middleware and extension methods. - AOT compatibility: reduces dependency on reflection at runtime.
- Consistent OpenAPI: tags, summaries, status codes and version are generated with the endpoint.
- Architectural alignment: HTTP is in API, orchestration goes through MediatR, and contracts can be shared with clients.
Flow at runtime
Program.csbuilds the application and calls the extension from the service or module endpoints.- The extension creates the API version set and the endpoint group, applies authorization/rate limiting and calls
MapEndpointsGenerated(). - The generated mapper calls the method
MapEndpointof each endpoint. - Each endpoint maps route, OpenAPI metadata, permission, tenant requirement and handler.
- The handler receives HTTP input, converts it to command/query, sends it to MediatR and returns a typed result.
Error definitions
Error definitions standardize known domain and application faults. They help APIs, handlers, logs, and frontends handle predictable problems without relying on stray messages, duplicate strings, or ad hoc decisions at each endpoint.
lino error-definition new --name <ErrorDefinitionName> --service <ServiceName> --module <ModuleName> --entity <EntityName> lino error-definition list --service <ServiceName> --module <ModuleName> --entity <EntityName>
In generated APIs, expected failures returned by commands and queries must be converted to ProblemDetails with status code, error code and secure message. The server may record detailed technical logs, but the HTTP response must remain consistent, secure, and localizable.
| Status | Typical usage | Example |
|---|---|---|
| 400 Bad Request | Invalid input or validation error. | Missing required field, invalid format, broken request rule. |
| 401 Unauthorized | Unauthenticated user. | Missing, expired or invalid token. |
| 403 Forbidden | Authenticated user without permission. | Permission required by RequirePermission not granted. |
| 404 Not Found | Non-existent resource or outside the permitted scope. | ProductNotFound, entity from another tenant, or unknown identifier. |
| 409 Conflict | State conflict or uniqueness violation. | Duplicate record, invalid transition, version conflict. |
Good practices
- Create error definitions for expected and reusable failures.
- Avoid returning technical exceptions or infrastructure details to the client.
- Maintain stable error codes so frontend, typed clients, and tests can react safely.
- Document in OpenAPI the status codes that the endpoint can produce.
- Use server-side logs for diagnostic details and secure messages in the public response.
