January 1, 2026 • 2 min read • SecBez Team
Writing Secure API Endpoints: A Checklist for Backend Developers
A practical security checklist for every new API endpoint, covering authentication, input validation, rate limiting, and error handling.
PlaybookEngineeringAppSec
Every new API endpoint is a potential attack surface. A structured checklist reduces the chance of shipping common vulnerabilities.
The endpoint security checklist
1. Authentication
- Does this endpoint require authentication?
- Is the auth check enforced by middleware, not manually in the handler?
- Are unauthenticated requests rejected with 401, not 403?
// Prefer middleware-based auth enforcement
router.use('/api/v1', authMiddleware)
// Avoid per-handler auth checks that can be forgotten
app.get('/api/v1/data', (req, res) => {
if (!req.user) return res.status(401).end() // Easy to forget
})
2. Authorization
- Does the endpoint check the user's role and permissions?
- Are resources scoped to the authenticated tenant or user?
- Can a user access resources belonging to another user?
3. Input validation
- Are all path parameters, query parameters, and body fields validated?
- Are types enforced (string, number, enum)?
- Are length limits set for string inputs?
- Are array inputs bounded to prevent denial of service?
// Use schema validation at the boundary
const schema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100),
role: z.enum(['admin', 'member', 'viewer']),
})
4. Rate limiting
- Is the endpoint rate limited?
- Are rate limits appropriate for the endpoint's use case?
- Are authentication endpoints (login, password reset) aggressively rate limited?
5. Error handling
- Do error responses avoid leaking internal details (stack traces, SQL errors, file paths)?
- Are all errors logged with sufficient context for debugging?
- Do 500 errors return a generic message to the client?
// Bad: leaks internal details
res.status(500).json({ error: err.message, stack: err.stack })
// Good: generic client response, detailed server log
logger.error('Failed to process request', { err, requestId })
res.status(500).json({ error: 'Internal server error', requestId })
6. Data exposure
- Does the response include only the fields the client needs?
- Are sensitive fields (password hashes, internal IDs, tokens) excluded?
- Are list endpoints paginated to prevent bulk data extraction?
Using this checklist
Add these items to your PR template for any PR that introduces a new endpoint. Not every item applies to every endpoint, but reviewing the list takes less than a minute and catches common oversights.
Automated scanning catches many of these patterns, but a human checklist catches the intent-level issues that scanners miss, like whether an endpoint should exist at all.