API Guidelines (RFC-39)
RFC-39 compliant API best practices for Java services.
When to use this skill
- Designing new REST API endpoints
- Reviewing API implementations for compliance
- Implementing error handling patterns
- Setting up pagination and filtering
- Configuring API versioning
- Implementing authentication and authorization
Skill Contents
Sections
- When to use this skill
- Quick Start
- Request/Response Patterns
- Error Handling
- Pagination
- Versioning
- Authentication
- References
- Related Rules
- Related Skills
Available Resources
π references/ - Detailed documentation
Quick Start
1. Controller Setup
@RestController
@RequestMapping("/api/v1/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
private final ResponseFactory responseFactory;
@GetMapping("/{orderId}")
public ResponseEntity<ApiResponse<OrderDto>> getOrder(
@PathVariable String orderId) {
OrderDto order = orderService.findById(orderId);
return responseFactory.ok(order);
}
}
2. Response Factory
@Component
public class ResponseFactory {
public <T> ResponseEntity<ApiResponse<T>> ok(T data) {
return ResponseEntity.ok(ApiResponse.success(data));
}
public <T> ResponseEntity<ApiResponse<T>> created(T data) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(data));
}
}
Request/Response Patterns
Standard Response Envelope
{
"success": true,
"data": { ... },
"meta": {
"requestId": "abc-123",
"timestamp": "2026-01-27T12:00:00Z"
}
}
Java Implementation
@Data
@Builder
public class ApiResponse<T> {
private boolean success;
private T data;
private ApiError error;
private ApiMeta meta;
public static <T> ApiResponse<T> success(T data) {
return ApiResponse.<T>builder()
.success(true)
.data(data)
.meta(ApiMeta.now())
.build();
}
}
@Data
@Builder
public class ApiMeta {
private String requestId;
private Instant timestamp;
public static ApiMeta now() {
return ApiMeta.builder()
.requestId(MDC.get("requestId"))
.timestamp(Instant.now())
.build();
}
}
Error Handling
Error Response Format
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request parameters",
"details": [
{
"field": "email",
"message": "must be a valid email address"
}
]
},
"meta": {
"requestId": "abc-123",
"timestamp": "2026-01-27T12:00:00Z"
}
}
Exception Handler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleNotFound(
ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("NOT_FOUND", ex.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Void>> handleValidation(
MethodArgumentNotValidException ex) {
List<FieldError> details = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(e -> new FieldError(e.getField(), e.getDefaultMessage()))
.toList();
return ResponseEntity.badRequest()
.body(ApiResponse.validationError(details));
}
}
Standard Error Codes
| Code | HTTP Status | Description |
|------|-------------|-------------|
| VALIDATION_ERROR | 400 | Invalid request parameters |
| UNAUTHORIZED | 401 | Missing or invalid authentication |
| FORBIDDEN | 403 | Insufficient permissions |
| NOT_FOUND | 404 | Resource not found |
| CONFLICT | 409 | Resource conflict |
| RATE_LIMITED | 429 | Too many requests |
| INTERNAL_ERROR | 500 | Unexpected server error |
Pagination
Request Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| page | 0 | Page number (0-indexed) |
| size | 20 | Page size (max 100) |
| sort | - | Sort field and direction |
Response Format
{
"success": true,
"data": [...],
"meta": {
"pagination": {
"page": 0,
"size": 20,
"totalElements": 150,
"totalPages": 8,
"hasNext": true,
"hasPrevious": false
}
}
}
Controller Example
@GetMapping
public ResponseEntity<ApiResponse<List<OrderDto>>> listOrders(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String sort) {
Pageable pageable = PageRequest.of(page, Math.min(size, 100));
Page<OrderDto> orders = orderService.findAll(pageable);
return responseFactory.paginated(orders);
}
Versioning
URL Path Versioning (Preferred)
@RestController
@RequestMapping("/api/v1/orders")
public class OrderV1Controller { }
@RestController
@RequestMapping("/api/v2/orders")
public class OrderV2Controller { }
Deprecation Headers
@GetMapping("/legacy-endpoint")
@Deprecated
public ResponseEntity<ApiResponse<Void>> legacyEndpoint() {
return ResponseEntity.ok()
.header("Deprecation", "true")
.header("Sunset", "Sat, 01 Mar 2026 00:00:00 GMT")
.header("Link", "</api/v2/new-endpoint>; rel=\"successor-version\"")
.body(ApiResponse.success(null));
}
Authentication
WebAPI Annotation
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
@GetMapping
@WebAPI(AuthType.PRIVATE) // Requires user authentication
public ResponseEntity<...> listOrders() { }
@GetMapping("/public-info")
@WebAPI(AuthType.PUBLIC) // No authentication required
public ResponseEntity<...> getPublicInfo() { }
@GetMapping("/internal")
@WebAPI(AuthType.INTERNAL) // Service-to-service only
public ResponseEntity<...> internalEndpoint() { }
}
Authentication Types
| Type | Usage |
|------|-------|
| PRIVATE | User-authenticated endpoints |
| PUBLIC | Unauthenticated, public endpoints |
| INTERNAL | Service-to-service communication |
References
| Reference | Description | |-----------|-------------| | references/response-patterns.md | Detailed response patterns |
Related Rules
- java-rest-api-guidelines - REST API standards (RFC-30/RFC-39)
Related Skills
| Skill | Purpose | |-------|---------| | rest-api | REST API implementation | | grpc-services-rfc-33 | gRPC service standards |
<!-- AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY --> <!-- Source: bitsoex/ai-code-instructions β java/skills/api-guidelines-rfc-39/SKILL.md --> <!-- To modify, edit the source file and run the distribution workflow -->