Appearance
API Standards Overview
Section: 15 — API
Last Updated: 2026-05-30
Scope: Versioning, response envelope, validation, Swagger, gateway routing
Standards at a Glance
All Microtec ERP APIs follow a consistent contract enforced by the Microtec.Web.Core NuGet package.
| Standard | Value |
|---|---|
| Versioning | URL path: /api/v1/, /api/v2/ |
| Response format | APIResponse<T> JSON envelope |
| Validation | FluentValidation (server-side) |
| Documentation | Swagger / OpenAPI 3.0 at /swagger |
| Auth | JWT Bearer (Authorization: Bearer) |
| Context | ERP Access Token (X-Access-Context) |
| Content type | application/json |
| Date format | ISO 8601 UTC |
URL Convention
/api/{version}/{domain}/{resource}[/{id}][/{sub-resource}]Examples
GET /api/v1/accounting/invoices
GET /api/v1/accounting/invoices/{id}
POST /api/v1/accounting/invoices
PUT /api/v1/accounting/invoices/{id}
DELETE /api/v1/accounting/invoices/{id}
GET /api/v1/hr/employees
GET /api/v1/hr/employees/{id}/contracts
POST /api/v1/hr/employees/{id}/contracts
GET /api/v1/accounting/invoices?page=1&limit=20&search=INV-001Naming Conventions
| Action | HTTP Method | URL Pattern |
|---|---|---|
| List all | GET | /api/v1/{domain}/{resources} |
| Get by ID | GET | /api/v1/{domain}/{resources}/{id} |
| Create | POST | /api/v1/{domain}/{resources} |
| Update | PUT | /api/v1/{domain}/{resources}/{id} |
| Partial update | PATCH | /api/v1/{domain}/{resources}/{id} |
| Delete | DELETE | /api/v1/{domain}/{resources}/{id} |
| Dropdown / lookup | GET | /api/v1/{domain}/{resources}/dropdown |
| Export | GET | /api/v1/{domain}/{resources}/export |
Handler Naming Convention
Backend CQRS handlers follow a strict naming pattern:
| Operation | Command/Query | Handler Name |
|---|---|---|
| Create | Command | Add{Entity}Command |
| Update | Command | Edit{Entity}Command |
| Delete | Command | Delete{Entity}Command |
| Get by ID | Query | GetById{Entity}Query |
| Get all / list | Query | GetAll{Entity}Query |
| Get dropdown | Query | GetDropdown{Entity}Query |
| Export | Query | Export{Entity}Query |
Response Envelope
See request-response.md for full envelope specification with examples.
All responses are wrapped in APIResponse<T>:
json
{
"success": true,
"data": { },
"message": "Operation successful",
"messageCode": null,
"validationErrors": null,
"meta": null
}HTTP status codes are always meaningful:
200 OK— successful read201 Created— resource created204 No Content— successful delete400 Bad Request— validation error401 Unauthorized— missing/invalid token403 Forbidden— insufficient permissions404 Not Found— resource not found409 Conflict— business rule violation422 Unprocessable Entity— semantic validation failure500 Internal Server Error— unexpected server error
FluentValidation
All command/query input DTOs have a corresponding AbstractValidator<T>:
csharp
public class AddEmployeeCommandValidator : AbstractValidator<AddEmployeeCommand>
{
public AddEmployeeCommandValidator()
{
RuleFor(x => x.FirstName)
.NotEmpty().WithMessage("First name is required")
.MaximumLength(100);
RuleFor(x => x.NationalId)
.NotEmpty()
.Length(10).WithMessage("National ID must be 10 digits")
.Matches(@"^\d{10}$");
RuleFor(x => x.HireDate)
.NotEmpty()
.LessThanOrEqualTo(DateTime.Today)
.WithMessage("Hire date cannot be in the future");
}
}Validation errors are automatically mapped to APIResponse<T>.validationErrors by the MediatR validation pipeline behavior (in Microtec.Web.Core).
Controller Pattern
Every controller action must have:
csharp
[ApiController]
[Route("api/v1/[controller]")]
[Authorize]
public class InvoicesController : ControllerBase
{
private readonly IMediator _mediator;
[HttpGet]
[ProducesResponseType(typeof(APIResponse<PagedResult<InvoiceDto>>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(APIResponse<object>), StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> GetAll([FromQuery] GetAllInvoiceQuery query)
=> Ok(await _mediator.Send(query));
[HttpGet("{id:guid}")]
[ProducesResponseType(typeof(APIResponse<InvoiceDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(APIResponse<object>), StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(Guid id)
=> Ok(await _mediator.Send(new GetByIdInvoiceQuery(id)));
[HttpPost]
[ProducesResponseType(typeof(APIResponse<Guid>), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(APIResponse<object>), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Create([FromBody] AddInvoiceCommand command)
{
var result = await _mediator.Send(command);
return CreatedAtAction(nameof(GetById), new { id = result.Data }, result);
}
}IMPORTANT
[ProducesResponseType] attributes are mandatory on all controller actions. They are required for accurate Swagger documentation and are enforced in code review.
Swagger / OpenAPI
Each microservice exposes Swagger UI at:
https://{service-host}/swaggerExample (local dev):
http://localhost:5001/swagger ← Accounting API
http://localhost:5002/swagger ← HR API
http://localhost:5003/swagger ← Inventory APISwagger is disabled in production and enabled in dev/stage via environment configuration:
json
{
"Features": {
"EnableSwagger": true
}
}Swagger Groups
Large services (e.g., Accounting) group endpoints by domain area:
csharp
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1-invoices", new() { Title = "Accounting - Invoices", Version = "v1" });
c.SwaggerDoc("v1-journals", new() { Title = "Accounting - Journals", Version = "v1" });
c.SwaggerDoc("v1-reports", new() { Title = "Accounting - Reports", Version = "v1" });
});Rate Limiting
All public endpoints are rate-limited via the API Gateway:
| Tier | Limit | Window |
|---|---|---|
| Standard (authenticated) | 1000 req | 1 minute |
| Export endpoints | 10 req | 1 minute |
| Auth endpoints | 20 req | 1 minute |
| Unauthenticated | 10 req | 1 minute |
Rate limit responses:
json
HTTP 429 Too Many Requests
Retry-After: 45
{
"success": false,
"data": null,
"message": "Rate limit exceeded. Retry after 45 seconds.",
"messageCode": 4290
}Localization
API responses support Arabic and English:
Accept-Language: ar ← Arabic error messages
Accept-Language: en ← English (default)Error messages in validationErrors are localized based on the Accept-Language header.