NOTE: These guidelines are heavily opinionated and may not be suitable for all use cases. Please adapt them to your specific requirements.
400 Bad Request: The request could not be understood by the server due to malformed syntax (often invalid query parameters or request body). Return a RFC 7807 problem detail object with a clear error message.401 Unauthorized: The client must authenticate itself to get the requested response. This is usually returned when theAuthorizationheader is missing or invalid.500 Internal Server Error: Return a RFC 7807 problem detail object. It is recommended to extend the problem detail object with atraceIdandrequestIdfield in this case to help with debugging.
- Use
GETfor fetching resources. GETrequests should be safe and idempotent.- Use appropriate cache headers to optimize performance.
200 OK: Successful response.404 Not Found: Resource not found or user does not have access.403 Forbidden: Authenticated user does not have access to this resource. It is recommended to use404instead of403forGETrequests to avoid exposing sensitive information.
- Use
POSTfor creating resources. POSTrequests should not be idempotent.POSTrequests should never be cached.
201 Created: Resource created successfully. Include aLocationheader with the URL of the new resource.200 OKor204 No Content: Resource created successfully but the resource cannot be identified by a URI. Use thePreferheader to determine the response format.403 Forbidden: Authenticated user does not have permission to create the resource.
- Use
PUTfor updating resources. The API may choose to create the resource if it does not exist. PUTrequests should be idempotent.PUTrequests should not be cached.
200 OKor204 No Content: Resource updated successfully. Use thePreferheader to determine the response format.201 Created: New resource created successfully.
- Use
DELETEfor deleting resources. DELETErequests should be idempotent.DELETErequests should not be cached.- Soft delete: Consider soft delete (marking the resource as deleted) instead of hard delete (removing the resource from the database).
200 OKor204 No Content: Resource deleted successfully. Use thePreferheader to determine the response format.202 Accepted: The request has been accepted for processing, but the processing has not been completed.404 Not Found: Resource not found or user does not have access.
- Use
PATCHfor partial updates to resources. PATCHrequests should be idempotent.PATCHrequests should not be cached.
[
{ "op": "test", "path": "/a/b/c", "value": "foo" },
{ "op": "remove", "path": "/a/b/c" },
{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
{ "op": "replace", "path": "/a/b/c", "value": 42 },
{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]200 OKor204 No Content: Resource updated successfully. Use thePreferheader to determine the response format.404 Not Found: Resource not found or user does not have access.
page- 1-based page numberpageSize- number of items per page
Provide default values for page and pageSize if not specified by the client. The default values should be page=1 and pageSize=10 (may vary based on resource).
Simple filtering is usable for most filtering requirements.
- Flexible parameters: Use query parameters that represent filterable fields.
- Example of simple filtering:
GET /resources?status=active&category=books - Example of range-based filtering:
GET /resources?minPrice=10&maxPrice=100 - Example of exclusion filtering:
GET /resources?excludeStatus=inactive
- Example of simple filtering:
- Multiple values: Use comma-separated values for filtering on multiple values of the same field.
- Example:
GET /resources?category=books,electronics
- Example:
- Date filtering: Use ISO 8601 date format for date filtering.
- Example:
GET /resources?createdAfter=2023-01-01T00:00:00Z&createdBefore=2023-12-31T23:59:59Z
- Example:
- Boolean filtering: Use
trueorfalsefor boolean filtering.- Example:
GET /resources?active=true
- Example:
- Filtering on nested fields: Use dot notation for filtering on nested fields.
- Example:
GET /resources?author.name=John
- Example:
For complex filtering requirements, consider using a filter parameter with a query language like OData.
- Syntax: Use a
filterparameter with a query language expression.- Example:
GET /resources?filter=status eq 'active' and price lt 100
- Example:
Read more on Microsoft's API guidelines.
- Syntax: Use a
sortparameter, with fields and optional direction- Example:
GET /resources?sort=-price,name(sort by price descending and name ascending)
- Example:
The default behavior should depend on the resource and should be documented.
{
"data": [],
"metadata": {
"page": 2,
"pageSize": 10,
"totalCount": 100,
"totalPages": 10
},
"links": {
"first": "/resources?page=1&pageSize=10",
"prev": "/resources?page=1&pageSize=10",
"self": "/resources?page=2&pageSize=10",
"next": "/resources?page=3&pageSize=10",
"last": "/resources?page=10&pageSize=10"
}
}data: Array of resourcesmetadata: Pagination metadatapage: Current page numberpageSize: Number of items per pagetotalCount: Total number of itemstotalPages: Total number of pages
links: Links to navigate through the paginated results (optional)first: Link to the first pageprev: Link to the previous page (if available)self: Link to the current pagenext: Link to the next page (if available)last: Link to the last page
public sealed class PaginationResponse<T>(IEnumerable<T> data, PaginationMetadata metadata, PaginationLinks? links = null)
{
[JsonPropertyName("data")]
public IEnumerable<T> Data { get; } = data;
[JsonPropertyName("metadata")]
public PaginationMetadata Metadata { get; } = metadata;
[JsonPropertyName("links")]
public PaginationLinks? Links { get; } = links;
}
public sealed class PaginationMetadata(int page, int pageSize, int totalCount)
{
[JsonPropertyName("page")]
public int Page { get; } = page;
[JsonPropertyName("pageSize")]
public int PageSize { get; } = pageSize;
[JsonPropertyName("totalCount")]
public int TotalCount { get; } = totalCount;
[JsonPropertyName("totalPages")]
public int TotalPages => (int)Math.Ceiling((double)TotalCount / PageSize);
}
public sealed class PaginationLinks(string first, string? previous, string self, string? next, string last)
{
[JsonPropertyName("first")]
public string First { get; } = first;
[JsonPropertyName("prev")]
public string? Previous { get; } = previous;
[JsonPropertyName("self")]
public string Self { get; } = self;
[JsonPropertyName("next")]
public string? Next { get; } = next;
[JsonPropertyName("last")]
public string Last { get; } = last;
}Preferheader: Use thePreferheader to request the server to return a minimal representation of the resource. This is not applicable forGETrequests.- Example:
Prefer: return=representation - Example:
Prefer: return=minimal
- Example:
X-Total-Count: Total number of itemsLink: A standard header for pagination links, following the format specified in RFC 8288.
- Validation errors: If the client provides invalid parameters (e.g.,
page< 1 or unsupported filter fields), return a400 Bad Requestwith a detailed error message. - Range errors: If
pageexceeds thetotalPages, respond with a404 Not Foundor a clear400 Bad Request.
- Cache control: Use appropriate cache headers to optimize API performance.
- Limit maximum
pageSize: To prevent performance degradation, set a maximum value forpageSize(e.g.,100items). - Cursor-based pagination: Consider using cursor-based pagination for large datasets to improve performance.