Back to blog

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.