Appearance
Request & Response Envelope
Section: 15 — API
Last Updated: 2026-05-30
Scope: APIResponse<T>, error format, pagination, validation errors
APIResponse<T> — The Standard Envelope
Every API response in Microtec ERP is wrapped in the APIResponse<T> envelope defined in Microtec.Contracts (Microtec.Domain NuGet package).
Success Response
json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"invoiceNumber": "INV-2026-00123",
"totalAmount": 15000.0000,
"status": "Draft"
},
"message": "Invoice retrieved successfully",
"messageCode": null,
"validationErrors": null,
"meta": null
}Paginated List Response
json
{
"success": true,
"data": [
{ "id": "...", "invoiceNumber": "INV-2026-00123" },
{ "id": "...", "invoiceNumber": "INV-2026-00124" }
],
"message": null,
"messageCode": null,
"validationErrors": null,
"meta": {
"total": 250,
"page": 1,
"limit": 20,
"totalPages": 13,
"hasNextPage": true,
"hasPreviousPage": false
}
}Create Response (201)
json
HTTP 201 Created
Location: /api/v1/accounting/invoices/550e8400-e29b-41d4-a716-446655440000
{
"success": true,
"data": "550e8400-e29b-41d4-a716-446655440000",
"message": "Invoice created successfully",
"messageCode": null,
"validationErrors": null,
"meta": null
}NOTE
Create operations return the new resource's GUID in data, not the full object. The client uses the returned ID to fetch the full resource if needed.
Error Responses
Validation Error (400)
json
HTTP 400 Bad Request
{
"success": false,
"data": null,
"message": "One or more validation errors occurred",
"messageCode": null,
"validationErrors": [
{
"field": "invoiceDate",
"message": "Invoice date cannot be in the future"
},
{
"field": "customerId",
"message": "Customer is required"
},
{
"field": "lines[0].quantity",
"message": "Quantity must be greater than 0"
}
],
"meta": null
}Business Rule Error (409 Conflict)
json
HTTP 409 Conflict
{
"success": false,
"data": null,
"message": "Employee already has an active contract for this period",
"messageCode": 9017,
"validationErrors": null,
"meta": null
}Not Found (404)
json
HTTP 404 Not Found
{
"success": false,
"data": null,
"message": "Invoice with ID '550e8400-...' was not found",
"messageCode": 4040,
"validationErrors": null,
"meta": null
}Unauthorized (401)
json
HTTP 401 Unauthorized
{
"success": false,
"data": null,
"message": "Authentication required",
"messageCode": 4010,
"validationErrors": null,
"meta": null
}Forbidden (403)
json
HTTP 403 Forbidden
{
"success": false,
"data": null,
"message": "You do not have permission to approve invoices",
"messageCode": 4030,
"validationErrors": null,
"meta": null
}Server Error (500)
json
HTTP 500 Internal Server Error
{
"success": false,
"data": null,
"message": "An unexpected error occurred. Please contact support with reference: REF-2026-05-30-abc123",
"messageCode": 5000,
"validationErrors": null,
"meta": null
}IMPORTANT
500 responses include a reference code (logged in Seq) but never expose stack traces, exception messages, or internal details in the response body.
The APIResponse<T> Class
csharp
// From Microtec.Contracts (Microtec.Domain NuGet package)
public class APIResponse<T>
{
public bool Success { get; set; }
public T? Data { get; set; }
public string? Message { get; set; }
public int? MessageCode { get; set; }
public IEnumerable<ValidationError>? ValidationErrors { get; set; }
public PaginationMeta? Meta { get; set; }
// Factory methods
public static APIResponse<T> Ok(T data, string? message = null)
=> new() { Success = true, Data = data, Message = message };
public static APIResponse<T> Fail(string message, int? code = null)
=> new() { Success = false, Message = message, MessageCode = code };
public static APIResponse<T> ValidationFail(IEnumerable<ValidationError> errors)
=> new()
{
Success = false,
Message = "One or more validation errors occurred",
ValidationErrors = errors
};
}
public record ValidationError(string Field, string Message);
public class PaginationMeta
{
public int Total { get; set; }
public int Page { get; set; }
public int Limit { get; set; }
public int TotalPages => (int)Math.Ceiling((double)Total / Limit);
public bool HasNextPage => Page < TotalPages;
public bool HasPreviousPage => Page > 1;
}Pagination
Request Parameters
All list endpoints accept standard pagination query parameters:
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
page | int | 1 | — | Page number (1-based) |
limit | int | 20 | 100 | Items per page |
search | string | — | — | Full-text search term |
sortBy | string | — | — | Field name to sort by |
sortDir | string | asc | — | asc or desc |
GET /api/v1/accounting/invoices?page=2&limit=50&search=INV-2026&sortBy=invoiceDate&sortDir=descPagedResult<T>
csharp
public class PagedResult<T>
{
public IEnumerable<T> Items { get; set; } = [];
public int Total { get; set; }
public int Page { get; set; }
public int Limit { get; set; }
}Used with APIResponse<PagedResult<T>>:
json
{
"success": true,
"data": {
"items": [...],
"total": 250,
"page": 2,
"limit": 50
},
"meta": {
"total": 250,
"page": 2,
"limit": 50,
"totalPages": 5,
"hasNextPage": true,
"hasPreviousPage": true
}
}NOTE
meta at the top level mirrors the pagination info from data. Clients may use either. The meta field is present whenever data is a paginated list.
MessageCode Reference
messageCode is a numeric code for programmatic error handling (independent of locale):
| Range | Category |
|---|---|
2xx | Success codes (rarely used) |
4010–4019 | Authentication errors |
4030–4039 | Authorization / permission errors |
4040–4049 | Not found errors |
4090–4099 | Conflict / business rule violations |
4290 | Rate limit exceeded |
5000–5099 | Server errors |
9000–9099 | HR domain errors |
9100–9199 | Accounting domain errors |
9200–9299 | Inventory domain errors |
9300–9399 | Sales domain errors |
Frontend applications use messageCode to show localized messages stored in the Angular translation files rather than relying on the server-sent message string.
Date and Number Formats
Dates
All dates in request/response bodies use ISO 8601 UTC:
json
{
"invoiceDate": "2026-05-30T00:00:00Z",
"createdOn": "2026-05-30T14:23:45.123Z",
"hireDate": "2026-01-01"
}NOTE
Date-only fields (like hireDate) use YYYY-MM-DD format without time. DateTime fields always include timezone (Z for UTC or +03:00 for AST).
Decimal Numbers
Decimal values are serialized as JSON numbers (not strings) with up to 4 decimal places:
json
{
"unitPrice": 150.5000,
"vatAmount": 22.5750,
"totalAmount": 173.0750
}Content Negotiation
| Header | Accepted Values |
|---|---|
Content-Type | application/json (required for POST/PUT/PATCH) |
Accept | application/json (default), application/pdf (export endpoints) |
Accept-Language | en (default), ar |
Request Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer {keycloak-jwt} |
X-Access-Context | Yes (ERP endpoints) | ERP Access Context Token |
Content-Type | Yes (body requests) | application/json |
Accept-Language | No | en or ar |
X-Api-Key | Internal only | 3bb564df-0f24-4ea6-82c1-d99f368cac8a for direct service-to-service |