API Guidelines
Principle 1 - What comes out must be a superset of what goes in
This principle means that the output data transfer object (DTO) returned from our APIs should always include everything from the input DTO, plus any additional data. Both the structure and field names must match. This approach ensures that consumers, like the frontend or other integrations, can take the output object, modify its values, and send it back to our update endpoints without needing any conversion. The system will handle any extra fields by simply ignoring them when they aren’t needed.
Example:
Input DTO (object sent to the API e.g. create endpoint):
{
"taxMode": "EXCLUSIVE",
"taxRate": 20.0,
"purchase": {
"amount": 50000.00
},
"renewal": {
"amount": 60000.00
},
"lineItems": [
{
"product": {
"id": "baa5aabe-3ad5-4200-a6a7-6bd6a2f31723"
},
"units": 100
}
]
}
Output DTO (object returned from the API):
{
"id": "253fe637-3c94-4f84-90fc-b79be8dab72a",
"taxMode": "EXCLUSIVE",
"taxRate": 20.0,
"purchase": {
"amount": 50000.00,
"totalNetAmount": 50000.00,
"totalTaxAmount": 10000.00,
"totalGrossAmount": 60000.00
},
"renewal": {
"amount": 60000.00,
"totalNetAmount": 60000.00,
"totalTaxAmount": 12000.00,
"totalGrossAmount": 72000.00
},
"lineItems": [
{
"product": {
"id": "baa5aabe-3ad5-4200-a6a7-6bd6a2f31723",
"name": "Symantec Endpoint Protection",
"vendor": {
"id": "8a998787-052b-44fc-81b7-e351b1f26adc",
"name": "Broadcom"
}
},
"units": 100
}
]
}
In this example, the output object contains all the data from the input object, but also includes additional calculated fields such as totalNetAmount, totalTaxAmount, and totalGrossAmount, as well as extra details like product and vendor names.
Crucially, the output object can be sent back to an update endpoint without causing issues—any extra fields not needed for the update will be safely ignored.
**This structure and field naming should also be reflected at the entity layer were possible, ensuring consistent sorting, queries and filtering. Use embeddable entity objects when needed to achieve this. **
Principle 2: Use Plural Nouns for Resource Names
Consistency
Always use plural nouns in API paths to represent collections of resources (e.g., /api/v1/cars). This is a widely accepted convention in RESTful API design, making it easier for developers to understand and predict the structure of your API.
Resource Representation:
Each API path should clearly represent a resource or a collection of resources. Naming should be intuitive and domain-specific, ensuring that paths are easy to navigate and self-explanatory for developers. For example:
/api/v1/users– A collection of user resources./api/v1/products– A collection of product resources.
Casing:
URL Paths: Should always use lowercase and kebab-case (words separated by hyphens) for resource names that require multiple words.
/api/v1/use-cases
Query Parameters: To maintain consistency with the Data Transfer Object (DTO) structure, query parameters should use CamelCase for multi-word parameters.
/api/v1/use-cases?createdBy=carlo
By following these conventions, your API will be more consistent, predictable, and user-friendly, enhancing the developer experience and reducing the likelihood of errors or misunderstandings in API usage.
Principle 3: Avoid Deeply Nested Routes
Flat Structure for Simplicity: Deeply nested paths, such as:
/api/v1/manufacturers/<manufacturerId>/cars
can become difficult to manage, especially as the complexity of relationships increases. Instead of creating deeply nested routes, opt for a flat structure and use query parameters to filter results by related entities.
Example: Rather than using the nested route above, use:
/api/v1/cars?manufacturerId=<manufacturerId>
This approach not only simplifies the structure but also makes the API more flexible and reusable. It allows you to reduce the number of unique endpoints, making maintenance easier while still enabling the ability to filter data based on relationships.
Performance Considerations: Nested routes can imply complex database joins or hierarchical relationships that may slow down performance. By using a flat structure with query parameters, you can enable more efficient filtering and database querying, avoiding unnecessary complexity in both your API and the underlying database queries.
Principle 4: Consistent Object Naming (Input, Output, Entity)
Consistent naming conventions enhance code readability and maintainability. This principle outlines our standards for naming Data Transfer Objects (DTOs), entities, and embedded objects in our codebase.
Input DTOs
Data Transfer Objects responsible for accepting user input (used in updating or modifying actions) should be named using the following convention:
<objectName>Input
Example: For an entity Vendor, the input DTO should be named VendorInput.
Validation on Input DTOs
Mandatory Validation: Input DTOs must have extensive validation annotations to ensure all input is correctly parsed and invalid data is rejected.
Complex Validation: For validation that cannot be handled via annotations, perform checks during the conversion from DTO to entity.
Error Handling: Use FieldErrorException to throw validation errors, specifying the relevant field name.
FieldErrorException.throwWith(
entity.getClass(),
"label",
String.format("A rating with label '%s' already exists!", entity.getLabel())
);
Output DTOs
Output DTOs (used for returning data to the user) should be named directly, without any suffix.
Example: Use Vendor rather than VendorInput for output DTOs
Entity Names
Entity names should simply follow the same name as the output DTO’s but with the word entity appended:
<objectName>Entity
Example: For a Vendor entity, the class name should be VendorEntity.
Embeddable Data Objects
When using embeddable data objects in hibernate these should be appended with the following:
<objectName>Data
Example: An embeddable address object should be named AddressData.
Principle 5: Field Naming Standard
Field names should always start with the noun (what it is), followed by any modifiers. This ensures that the output JSON can be easily viewed in alphabetical order, grouping related fields together.
- Rationale: Unlike English, where descriptors come before the noun (e.g., "beautiful fast red car"), starting with the noun in code helps with sorting and readability.
| Correct Field Name | Incorrect Field Name |
|---|---|
✅carColor | ❌colorCar |
✅userAge | ❌ageUser |
✅productPrice | ❌priceProduct |
✅orderDate | ❌dateOrder |
Do not repeat the object name at field level
When creating objects consider that the object name already represents what it is and repeating this at the field / attribute level does not make sense e.g. consider a product object:
| Correct Field Name | Incorrect Field Name |
|---|---|
✅id | ❌productId |
✅name | ❌productName |
✅description | ❌productDescription |
By adhering to this field naming standard, we promote consistency and improve the maintainability of our codebase.
Principle 6: Handle Relationships with Care
Effectively managing object relationships is crucial for building efficient APIs and responsive frontends. This principle provides guidelines on handling parent-child relationships in Data Transfer Objects (DTOs) to include necessary information without overcomplicating data structures.
Include Essential Parent Information For many-to-one relationships (child to parent), include minimal essential details about parent objects in your DTOs. This enables the frontend to display necessary data with a single request.
Example: Vendor > Product > Feature Consider a Feature object associated with a Product and a Vendor. The Feature DTO should include key details of its parent Product and Vendor:
{
"id": "258f4d05-534b-4bca-8c2d-63d88da5cb82",
"name": "SSL Interception",
"description": "This is an example description",
"product": {
"id": "0013c5e7-277c-442b-a375-9071ec853897",
"name": "ProxySG",
"vendor": {
"id": "fd8adaa7-65cd-4e0f-a7ac-4b6f4b24e4b4",
"name": "Broadcom"
}
}
}
- Benefit: The frontend can display all necessary information without additional API calls.
- Alignment: This follows Principle 1: What comes out must be a superset of what goes in.
Limit Nesting Depth
While including parent information is beneficial, limit the depth of nested relationships to maintain performance and clarity.
- Provide Minimal Data: Include only essential fields (e.g.,
id,name). - Avoid Over-Nesting: Limit nesting to immediate parents unless deeper data is required.
- Consider UI Needs: Include data necessary for the frontend
Handle One-to-Many Relationships Separately
For one-to-many relationships (parent to multiple children), avoid embedding all child objects within the parent DTO.
- Use Separate Endpoints: Frontend should fetch child data through dedicated, paginated endpoints.
Example:
GET /api/v1/products?vendorId=<vendorId>
- Benefits:
- Reduces payload size and improves response times.
- Allows clients to control data volume via pagination.
- Alignment: This aligns with Principle 3: Avoid Deeply Nested Routes.
Principle 7: Follow CRUD Principles for Public API’s
Following CRUD principles ensures that your API aligns with well-known standards, making it easier for developers to understand and predict the behavior of your endpoints based on the HTTP methods used. Each method corresponds to a specific type of operation (Create, Read, Update, Delete), which provides consistency and clarity.
Here's a table that outlines the HTTP methods aligned with our CRUD operations :
| Method | Naming Convention | **Response ** | Example Path | Endpoint Description |
|---|---|---|---|---|
| GET | [resourceName]Options | Single | /api/v1/contracts/options | Provides input options for the given resource. |
| GET | [resourceName]Summary | Single | /api/v1/contracts/summary | Provides a single summary of all resources of the given type. |
| GET | [resourceName]List | Collection | /api/v1/contracts | Lists all records of the given resource, normally paginated. |
| GET | [resourceName]Get | Single | /api/v1/contracts/<id> | Gets a specific instance of the given object by id. |
| POST | [resourceName]Create | Single | /api/v1/contracts | Strictly creates a new instance of the given object. |
| PUT | [resourceName]Update | Single | /api/v1/contracts/<id> | Updates a specific instance of the given object by id. |
| DELETE | [resourceName]Delete | Single | /api/v1/contracts/<id> | Deletes a specific instance of the given object by id. |
| PATCH | [functionName]Action | Single | /api/v1/contracts/bulk-renew | Perform an encapsulated modifying action. e.g. sending an email reminder that updates ‘lastNotifiedOn’ |
This structure promotes consistency, predictability, and clarity, making the API more user-friendly for developers consuming it.
Principle 8: Global & Scoped Queries
Agentic Coding
Best practices on using agentic coding in your local development environment.
LLM Prompts & Langfuse Integration
Step-by-step guide for backend developers on adding new prompts, modifying existing prompts across platform-api and api-cps, integrating with Langfuse and esp-prompts, Quartz reload scheduling, and coordinating Data/AI team benchmarking.

