ensi-api-design
Ensi API Design Guide
This skill ensures that all API endpoints in Ensi projects follow the official API Design Guide. Apply these standards when designing, implementing, or reviewing REST APIs.
Core Principles
Always follow these priorities:
- REST architectural style for all API implementations
- JSON format for all data transfer
- Consistent naming conventions (kebab-case for URLs, snake_case for fields)
- Versioning in URL paths (/v1/, /v2/)
- Standard response format with data, errors, and meta fields
- Design-first approach with OpenAPI 3.0 specifications
General Rules
URL Structure
Resource names MUST be plural (except single-instance resources):
Good: POST /v1/users
Bad: POST /v1/user
Good: GET /v1/profile (single instance allowed)
Resource names MUST be kebab-case:
Good: GET /v1/customer-addresses
Bad: GET /v1/customerAddresses
Query parameters and body fields MUST be snake_case:
Good: ?last_login_at=2020-01-01
Bad: ?lastLoginAt=2020-01-01
API version MUST always be in URL:
Good: GET /v1/users
Bad: GET /users
Non-existent pages MUST return 404 with proper error format:
{
"data": null,
"errors": [
{
"code": "NotFoundHttpException",
"message": "Resource not found"
}
]
}
Field Formats
Integer for all entity IDs:
Good: { "id": 12345 }
Bad: { "id": "12345" }
ISO-8601 for datetime fields (UTC):
Good: { "updated_at": "2020-01-01T15:47:21.000000Z" }
Bad: { "updated_at": "2020-01-01 15:47:21" }
ISO-8601 full date for dates:
Good: { "birthday": "1990-01-25" }
Bad: { "birthday": "1990/01/25" }
Prices in kopecks (integer):
Good: { "price": 1000 } // 10 rubles
Bad: { "price": 10.00 }
Response Format
All responses MUST contain only these fields:
data - main response object:
null- when no dataobject- single entity responsearray- list of entities
errors - optional array of error objects:
{
"code": "ErrorType",
"message": "Human-readable error description",
"meta": {
"additional": "debug information"
}
}
meta - optional object with additional information:
{
"meta": {
"pagination": { /* ... */ },
"debug": { /* ... */ }
}
}
API Architecture Types
Back Services (Internal API)
Design APIs around resources with standard CRUD methods.
Standard Methods Table
| Method | HTTP Method | Purpose |
|---|---|---|
| Get | GET /v1/users/{id} |
Get entity by ID |
| Create | POST /v1/users |
Create new entity |
| Replace | PUT /v1/users/{id} |
Update all fields |
| Patch | PATCH /v1/users/{id} |
Update specific fields |
| Delete | DELETE /v1/users/{id} |
Delete entity |
| Search | POST /v1/users:search |
Search with filters |
| SearchOne | POST /v1/users:search-one |
Get single entity by filters |
Get Method
Request:
GET /v1/users/17?include=addresses,loyality_cards
Response:
{
"data": {
"id": 17,
"name": "John Doe",
"last_login_at": "2020-01-01T15:47:21.000000Z",
"addresses": [/* ... */],
"loyality_cards": [/* ... */]
}
}
Create Method
Request:
POST /v1/users
{
"name": "John Doe",
"last_login_at": "2020-01-01T15:47:21.000000Z"
}
Response:
{
"data": {
"id": 1006779,
"name": "John Doe",
"last_login_at": "2020-01-01T15:47:21.000000Z"
}
}
Replace Method
Request:
PUT /v1/users/1006779
{
"name": "John Doe",
"last_login_at": null
}
Requirements:
- MUST be idempotent
- Optional fields set to default if omitted
idfield MUST be ignored
Response: Same as Get method
Patch Method
Request:
PATCH /v1/users/1006779
{
"name": "Jane Doe"
}
Requirements:
- Only specified fields are updated
idfield MUST be ignored
Response: Same as Get method
Delete Method
Request:
DELETE /v1/users/1006779
Requirements:
- MUST NOT return 404 if already deleted
- Deleting methods should be idempotent
Search Method
Request:
POST /v1/users:search
{
"sort": ["-last_login_at", "id"],
"filter": {
"id": [12125, 1006779],
"last_login_gte": "2020-01-01T15:47:21.000000Z"
},
"include": ["roles", "loyality_cards"],
"pagination": { /* ... */ }
}
Requirements:
- POST method to avoid GET limitations
- All fields optional
Response:
{
"data": [/* array of users */],
"meta": {
"pagination": { /* ... */ }
}
}
SearchOne Method
Request format: Same as Search
Response: Single object (same format as Get)
Additional Methods
Format: POST /v1/users:method-name
Examples:
POST /v1/users:mass-delete
POST /v1/offer-certificates/5:upload-file
Requirements:
- MUST use POST
- Method name added via colon
- Request/response formats flexible but similar to standard methods
Filters
All filters MUST be in single filter object:
{
"filter": {
"active": true,
"id": [1, 2],
"last_login_at_gte": "2020-01-01T15:47:21.000000Z"
}
}
Rules:
- No modifier = equality operator (=)
- Array values = OR condition
- All filters combined with AND
Filter Modifiers
| Modifier | Description | Array Support |
|---|---|---|
_gt |
Greater than | - |
_gte |
Greater or equal | - |
_lt |
Less than | - |
_lte |
Less or equal | - |
_not |
Not equal | + |
_and |
AND instead of OR for arrays | + |
_like |
LIKE '%value%' | + |
_llike |
LIKE '%value' | + |
_rlike |
LIKE 'value%' | + |
_regex |
Regex match | + |
has_* |
Has related entities | - |
*_count |
Count of related entities | + |
Examples:
{
"filter": {
"last_login_at_gte": "2020-01-01",
"id_not": [1, 2],
"name_like": "john",
"roles_count_gte": 2
}
}
Modifiers can be combined:
{
"filter": {
"orders_count_gte": 5
}
}
Reserved Filters
| Filter | Description | Array Support |
|---|---|---|
id |
Primary key filter | + |
trashed |
Soft delete filter | - |
query |
DSL query string | - |
trashed values:
"with"- include trashed"only"- only trashed- omitted - only non-trashed
Pagination
Two pagination types MUST be supported:
Offset Pagination
Request:
{
"pagination": {
"type": "offset",
"offset": 40,
"limit": 20
}
}
Response:
{
"meta": {
"pagination": {
"type": "offset",
"offset": 40,
"limit": 20,
"total": 253
}
}
}
Cursor Pagination
Request:
{
"pagination": {
"type": "cursor",
"cursor": "eyJpZCI6MTAsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0",
"limit": 20
}
}
Response:
{
"meta": {
"pagination": {
"type": "cursor",
"cursor": "eyJpZCI6MTAsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0",
"limit": 20,
"next_cursor": "eyJpZCI6MjEsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX1",
"previous_cursor": "eyJpZCI6MTIsIl9wb2ludHNUb05leHRJdGVtcyI6ZmFsc2V9"
}
}
}
Default pagination:
{
"type": "offset",
"offset": 0,
"limit": 10
}
Limit behavior:
- 0 = return 0 elements
- -1 = return all elements (can be disabled)
-
max limit = auto-reduce to max (can be configured)
Subresources
NOT RECOMMENDED - use separate resources instead.
Exceptions when subresources are acceptable:
- Non-unique identifiers (requires {parent_id, child_id} pair)
- All conditions met:
- Clear hierarchy
- Resource cannot exist without parent
- No meaning outside parent context
- Constant relationship
Max nesting depth: 2
Module prefix is NOT a subresource:
/api/v1/customers/addresses // OK (module prefix)
Includes
Use include parameter for related resources:
Get method:
GET /v1/users/17?include=addresses_count,loyality_cards,profile
Search method:
{
"include": ["addresses_count", "loyality_cards", "profile"]
}
Include types:
- Object - X to One relation (e.g.,
profile) - Array - X to Many relation (e.g.,
loyality_cards) - Number - Count with
_countsuffix (e.g.,addresses_count)
Response:
{
"data": {
"id": 17,
"addresses_count": 7,
"loyality_cards": [/* ... */],
"profile": {/* ... */}
}
}
Limitations:
- No complex sorting, filtering, or pagination for includes
- Use separate resources for complex cases
File Upload
Large files MUST use separate POST request:
Content-Type: multipart/form-data
POST /v1/offer-certificates/5:upload-file
Gateway Services (External API)
Design for external consumers (facades, external systems).
Principles
- Specialized API, not flexible
- Don't push business logic to consumers
- One task = one method
- BFF: One screen = one GET method + save methods
Module Selection
API for frontend:
- Module matches frontend section (lk, catalog, cart, checkout)
Connectors:
- Module matches domain owner (orders, customers)
- Small connectors may not need domain separation
When to Use Standard Methods
Use standard Back-service methods when:
- Consumer uses CRUD approach (e.g., address management on facade)
- Merge backend resources if needed for frontend
- Resource search needed
- Adapt standard approach (e.g., always include required relations)
HTTP Status Codes
Use ONLY these status codes:
| Code | Usage |
|---|---|
| 200 OK | All successful responses (unless 201 is auto-generated) |
| 201 Created | When framework auto-generates it (otherwise prefer 200) |
| 401 Unauthorized | Authentication required |
| 403 Forbidden | Authorized but not permitted |
| 404 Not Found | Resource/instance doesn't exist |
| 400 Bad Request | Any client error |
| 500 Internal Server Error | Application error (not client fault) |
Documentation MUST list all possible codes for each endpoint.
API Versioning
Breaking changes strategy:
- Known consumers, easy change → update immediately
- Unknown/hard to update → evolution principle, technical debt cleanup
- Massive changes, impossible evolution → versioning
Versioning rules:
- Global versioning (one version per client)
- Increment version (/v1/ → /v2/)
- Version MUST be in URL
- Separate OpenAPI specs and controllers per version
- Keep all versions in single swagger (use Servers selector, descending order)
API Documentation
Requirements:
- All api endpoints MUST use OpenAPI 3.0 specification
- Documentation MUST be accessible via Stoplight browser
- Design-first approach
- Keep all versions in single swagger
- Select version via Servers dropdown
- Arrange versions in descending order
- Follow OpenAPI specification requirements
When Applying This Skill
Design new API endpoint:
- Determine if Back-service or Gateway service
- Choose appropriate standard methods or create custom
- Define request/response formats
- Implement filters with proper modifiers
- Add pagination (both offset and cursor)
- Design includes if needed
- Document with OpenAPI 3.0
Review existing API:
- Check URL structure (kebab-case, plural, versioned)
- Verify field formats (integer IDs, ISO-8601 dates)
- Validate response format (data, errors, meta)
- Ensure proper HTTP status codes
- Check pagination implementation
- Review filter modifiers and reserved filters
- Verify OpenAPI documentation
Implement API in Laravel:
- Create controller with proper naming
- Define routes with kebab-case URLs
- Use snake_case for request body fields
- Implement standard CRUD methods
- Add search endpoint with filters
- Implement pagination
- Create FormRequest classes
- Write API tests
Common Pitfalls to Avoid
- ❌ Singular resource names (use plural)
- ❌ camelCase in URLs (use kebab-case)
- ❌ Missing API version in URL
- ❌ Using string IDs instead of integers
- ❌ Incorrect datetime formats (use ISO-8601 UTC)
- ❌ Prices in rubles (use kopecks)
- ❌ Missing pagination in list methods
- ❌ Using GET for search (use POST)
- ❌ Subresources when separate resources are better
- ❌ Missing OpenAPI documentation
- ❌ Using HTTP status codes outside the approved list
Remember: Consistency with existing APIs in the project is paramount. When working with established codebases, maintain consistency even if it differs slightly from these guidelines.
More from ensi-platform/skills
ensi-code-style
Enforce PHP and Laravel code style according to Ensi guidelines. Use this skill whenever writing, modifying, or reviewing PHP/Laravel code in Ensi projects, creating new classes (Controllers, Models, Actions, Events, etc.), refactoring existing code, setting up validation, routes, migrations, or working with Domain/Http layers in Laravel applications. Also trigger when checking code style compliance or generating Laravel components to ensure they follow Ensi conventions.
9ensi-openapi
Работайте с OpenAPI спецификациями в сервисах Ensi. Используйте этот скилл всегда, когда пользователь упоминает OpenAPI, API спецификации, создание endpoints, схем, перечислений или работу с yaml файлами в `public/api-docs/`. Также используйте при упоминании Swagger, спецификаций API, создании новых API endpoints или обновлении существующей документации API.
9ensi-models
Work with Eloquent models in Ensi projects following project standards. Use this skill whenever the user mentions creating, modifying, or working with Laravel Eloquent models, database models, model factories, model relationships, model scopes, database migrations, or database tables in the Ensi context. This includes requests like "create a new model", "add a relationship to this model", "create a factory for this model", "add a scope method", "create a migration", or any work related to database modeling and Laravel Eloquent patterns in Ensi projects.
8ensi-query-builder
Создание и модификация Query классов для spatie/laravel-query-builder в Ensi сервисах. Использовать при работе с Query классами, фильтрами API, search endpoints, allowedFilters, allowedSorts, allowedIncludes, а также при упоминании фильтрации в контроллерах API, создании search-эндпоинтов, добавлении фильтров к моделям.
8ensi-meta
Создание и модификация meta эндпоинтов в Ensi сервисах. Использовать при работе с мета-информацией полей, Field классами, ModelMetaResource, EnumInfo классами, а также при упоминании meta методов в контроллерах, создании мета-эндпоинтов, настройке фильтров/сортировки для фронтенда.
7ensi-kafka
Work with Kafka in Ensi Laravel services following project standards. Use this skill whenever the user mentions creating Kafka producers, consumers, observers, or any Kafka-related work in Ensi context. This includes requests like "create a Kafka producer for Product entity", "set up Kafka consumer", "add observer to send events", "create Kafka payload", "configure Kafka topics", or any work related to Kafka integration, message producers/consumers, observers, or event-driven architecture in Ensi Laravel projects.
6