// Validate REST API standards compliance (versioning, naming, HTTP methods, status codes, pagination, Swagger). Use when checking endpoints before deployment, reviewing API design, or ensuring documentation completeness (e.g., "Validate User API", "Check Product endpoints").
| name | api-validator |
| description | Validate REST API standards compliance (versioning, naming, HTTP methods, status codes, pagination, Swagger). Use when checking endpoints before deployment, reviewing API design, or ensuring documentation completeness (e.g., "Validate User API", "Check Product endpoints"). |
| allowed-tools | Read, Glob, Grep |
Validate REST API design standards compliance including versioning, naming conventions, HTTP methods, status codes, pagination, and Swagger documentation.
Performs comprehensive API standards validation:
/v1/ prefix on all routesUse when you need to:
Examples:
All controllers MUST be prefixed with /v1/:
// ✅ CORRECT
@JsonController('/v1/users')
export class UserController {}
@JsonController('/v1/sms-messages')
export class SmsMessageController {}
// ❌ WRONG
@JsonController('/users') // Missing version
@JsonController('/api/users') // Wrong format
Must be:
/v1/users not /v1/user/v1/sms-messages/v1/users not /v1/getUsers// ✅ CORRECT
'/v1/users'
'/v1/products'
'/v1/sms-messages'
'/v1/order-items'
// ❌ WRONG
'/v1/user' // Singular
'/v1/getUsers' // Verb
'/v1/Users' // Wrong casing
'/v1/sms_messages' // Underscores
Expected patterns:
Error status codes:
List endpoints MUST support pagination with DTOs:
// ✅ CORRECT
export class QueryEntityDto {
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(1)
limit?: number = 20;
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(0)
offset?: number = 0;
sortBy: z.enum(['createdAt', 'name']).optional(),
order: z.enum(['asc', 'desc']).default('desc'),
});
// Response format
{
items: Entity[],
total: number,
limit: number,
offset: number
}
All endpoints MUST have:
.post('/', controller.create.bind(controller), {
body: CreateSchema,
detail: {
summary: 'Create entity', // Required
description: 'Detailed description', // Recommended
tags: ['Entities'], // Required
responses: { // Required
201: { description: 'Created' },
400: { description: 'Invalid input' },
409: { description: 'Duplicate' },
},
},
})
Checks:
/v1/Reports:
CRITICAL: Missing API version
File: src/contexts/user/presentation/user.routes.ts:5
Issue: Route prefix '/users' lacks /v1/
Fix: Change to '/v1/users'
Checks:
Reports:
CRITICAL: Singular resource name
File: src/contexts/user/presentation/user.routes.ts:5
Issue: Resource '/v1/user' is singular
Fix: Change to '/v1/users'
CRITICAL: Verb in resource name
File: src/contexts/user/presentation/user.routes.ts:10
Issue: Resource '/v1/getUsers' contains verb
Fix: Use '/v1/users' with GET method
Checks:
Reports:
CRITICAL: Wrong status code for POST
File: src/contexts/user/presentation/user.routes.ts:15
Issue: POST endpoint documents 200, should be 201
Fix: Change response code to 201 in Swagger detail
Checks:
Reports:
WARNING: Missing error mapping
File: src/contexts/user/presentation/user.controller.ts:20
Issue: EntityNotFoundError not mapped to HttpException
Fix: Add error mapping:
if (error instanceof EntityNotFoundError) {
throw new HttpException(404, error.message, error.code);
}
Checks:
limit parameteroffset parameterReports:
CRITICAL: Missing pagination
File: src/contexts/user/presentation/schemas/query-user.schema.ts
Issue: Query schema lacks limit and offset
Fix: Add pagination fields:
limit: z.coerce.number().min(1).max(100).default(10),
offset: z.coerce.number().min(0).default(0),
Checks:
detail objectsummary field presenttags array presentresponses object with status codesReports:
WARNING: Incomplete Swagger documentation
File: src/contexts/user/presentation/user.routes.ts:15
Issue: POST endpoint missing 'responses' in detail
Fix: Add responses object:
responses: {
201: { description: 'User created' },
400: { description: 'Invalid input' },
409: { description: 'Email exists' },
}
Checks:
.bind(controller)Reports:
CRITICAL: Controller method not bound
File: src/contexts/user/presentation/user.routes.ts:12
Issue: .post('/', controller.create) missing .bind()
Fix: Add .bind(controller): .post('/', controller.create.bind(controller))
// ❌ WRONG
new Elysia({ prefix: '/users' })
// ✅ CORRECT
new Elysia({ prefix: '/v1/users' })
// ❌ WRONG - Singular
new Elysia({ prefix: '/v1/user' })
// ❌ WRONG - Verb
.get('/getUser', ...)
// ✅ CORRECT
new Elysia({ prefix: '/v1/users' })
.get('/:id', ...)
// ❌ WRONG
.post('/', ..., {
detail: {
responses: {
200: {} // Should be 201 for POST
}
}
})
// ✅ CORRECT
.post('/', ..., {
detail: {
responses: {
201: { description: 'Created' }
}
}
})
// ❌ WRONG
export const QuerySchema = z.object({
search: z.string().optional(),
});
// ✅ CORRECT
export const QuerySchema = z.object({
limit: z.coerce.number().min(1).max(100).default(10),
offset: z.coerce.number().min(0).default(0),
search: z.string().optional(),
});
// ❌ WRONG
.post('/', controller.create.bind(controller), {
body: CreateSchema,
})
// ✅ CORRECT
.post('/', controller.create.bind(controller), {
body: CreateSchema,
detail: {
summary: 'Create user',
tags: ['Users'],
responses: {
201: { description: 'User created' },
400: { description: 'Invalid input' },
},
},
})
The validator is read-only - it never modifies code. After review: