39.1 Introduction
Chapter 38 established authentication, sessions, authorization, ownership, and permission boundaries.
The next layer is the API architecture.
The API is the controlled communication boundary between the client application and the server-side platform.
A well-designed API must provide more than endpoints. It must establish consistent rules for:
- request validation;
- authentication;
- authorization;
- routing;
- business logic;
- database access;
- error handling;
- rate limiting;
- logging;
- request tracing;
- response formatting;
- versioning;
- security controls.
The fundamental architecture is:
Client
│
▼
HTTP Request
│
▼
API Router
│
▼
Request Validation
│
▼
Authentication
│
▼
Authorization
│
▼
Rate Limit / Policy
│
▼
Service Layer
│
▼
Repository
│
▼
Database / External Service
│
▼
Response
39.2 API Design Principles
The API should follow several principles.
Principle 1 — Explicit boundaries
Every endpoint should have a clearly defined purpose.
Principle 2 — Server-side trust
The server must independently validate security-sensitive information.
Principle 3 — Consistent responses
Clients should not have to guess how different endpoints behave.
Principle 4 — Small responsibilities
A route should coordinate an operation rather than contain the entire application's business logic.
Principle 5 — Observability
Important requests should be traceable without exposing secrets.
39.3 API Directory Structure
A Next.js App Router implementation can use:
src/
└── app/
└── api/
├── auth/
│ ├── login/
│ │ └── route.ts
│ └── logout/
│ └── route.ts
│
├── users/
│ └── route.ts
│
├── projects/
│ ├── route.ts
│ └── [id]/
│ └── route.ts
│
├── conversations/
│ ├── route.ts
│ └── [id]/
│ └── route.ts
│
└── files/
├── route.ts
└── [id]/
└── route.ts
The structure should reflect the application's resource model.
39.4 Resource-Oriented API
The initial API can be organized around resources.
For example:
/api/users
/api/projects
/api/conversations
/api/messages
/api/files
Individual resources can use:
/api/projects/{projectId}
/api/conversations/{conversationId}
/api/files/{fileId}
This makes the API easier to understand.
39.5 HTTP Methods
Typical operations are:
| Method | Purpose |
|---|---|
| GET | Retrieve data |
| POST | Create a resource or initiate an operation |
| PATCH | Partially update a resource |
| PUT | Replace a resource where appropriate |
| DELETE | Delete or deactivate a resource |
For example:
GET /api/projects
POST /api/projects
GET /api/projects/{id}
PATCH /api/projects/{id}
DELETE /api/projects/{id}
The exact semantics should remain consistent across the application.
39.6 Request Lifecycle
A request should pass through a predictable lifecycle.
HTTP Request
│
▼
Request ID
│
▼
Parse Request
│
▼
Validate Input
│
▼
Authenticate
│
▼
Authorize
│
▼
Rate Limit
│
▼
Service
│
▼
Repository
│
▼
External Systems
│
▼
Response
│
▼
Logging
Not every endpoint needs every step, but security-sensitive endpoints should have appropriate controls.
39.7 Request IDs
Every important request should have a correlation identifier.
Example:
requestId = "req_..."
The request ID can appear in server logs.
A simplified flow:
Client Request
│
▼
Generate Request ID
│
├──────────────► Application Logs
│
▼
Process Request
│
▼
Response
If a user reports a problem, support personnel can use the request ID to locate relevant server-side logs without exposing internal details.
39.8 Input Validation
Never assume that JSON submitted by the client is valid.
Example request:
{
"name": "Research Project"
}
The server should verify:
name exists
name is a string
name length is acceptable
name does not violate application rules
Validation should happen before business logic.
39.9 Validation Layer
A reusable validation module can be created:
src/lib/validation.ts
For example, using a schema-validation library:
import { z } from "zod";
export const createProjectSchema =
z.object({
name: z
.string()
.trim()
.min(1)
.max(200),
description: z
.string()
.trim()
.max(2000)
.optional(),
});
The API can then validate:
const body =
createProjectSchema.parse(
await request.json()
);
The exact validation library is an implementation choice, but the principle remains the same.
39.10 Validation vs Sanitization
Validation asks:
Is this input acceptable?
Sanitization asks:
How should potentially unsafe or unwanted input be normalized?
These should not be treated as interchangeable.
For example:
Validation
"Is the project name a valid string?"
Output handling
"How should this string be displayed safely?"
Every output context must be handled appropriately.
39.11 Authentication Middleware
Protected routes should resolve the current user.
Conceptually:
const user = await requireUser();
If no authenticated identity exists:
401 Unauthorized
The route should stop processing.
39.12 Authorization Middleware
Authentication does not guarantee access.
For project operations:
const project =
await getUserProject(
user.id,
projectId
);
If the user does not own or otherwise have permission to access the project:
403 Forbidden
or an intentionally non-disclosing resource response, depending on the application's security policy.
39.13 API Response Contract
Responses should follow a consistent structure.
Successful response:
{
"data": {
"id": "project-id",
"name": "Research Project"
}
}
Error response:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request."
}
}
The client can then reliably process the response.
39.14 Error Codes
Instead of relying only on human-readable messages, APIs can provide stable error codes.
Examples:
VALIDATION_ERROR
UNAUTHORIZED
FORBIDDEN
NOT_FOUND
CONFLICT
RATE_LIMITED
INTERNAL_ERROR
SERVICE_UNAVAILABLE
The client can use the code for behavior while the message remains user-friendly.
39.15 HTTP Status Codes
A reasonable baseline is:
200 OK
201 Created
204 No Content
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
409 Conflict
429 Too Many Requests
500 Internal Server Error
503 Service Unavailable
The application should use them consistently.
39.16 Project Creation Endpoint
A simplified endpoint:
import { NextResponse } from "next/server";
import { requireUser } from "@/lib/auth";
import { createProject } from "@/services/project.service";
export async function POST(
request: Request
) {
try {
const user = await requireUser();
const body = await request.json();
const project =
await createProject(
user.id,
body
);
return NextResponse.json(
{
data: project,
},
{
status: 201,
}
);
} catch (error) {
console.error(error);
return NextResponse.json(
{
error: {
code: "INTERNAL_ERROR",
message:
"Unable to create project.",
},
},
{
status: 500,
}
);
}
}
In a production implementation, validation and error classification should be explicit rather than relying on one generic catch block.
39.17 Service Layer
The service should own the business operation.
Example:
export async function createProject(
userId: string,
input: {
name: string;
description?: string;
}
) {
if (!input.name.trim()) {
throw new Error(
"Project name is required."
);
}
return prisma.project.create({
data: {
name: input.name.trim(),
description:
input.description?.trim(),
ownerId: userId,
},
});
}
A more mature implementation would separate validation from business logic and map domain errors to stable API errors.
39.18 Repository Boundary
The service should not necessarily contain raw SQL or large ORM queries.
Instead:
Service
│
▼
Repository
│
▼
Prisma
Example:
export async function
createProjectRecord(data: {
name: string;
description?: string;
ownerId: string;
}) {
return prisma.project.create({
data,
});
}
This keeps data-access logic reusable.
39.19 API Versioning
As the platform evolves, API contracts may change.
A versioned design can use:
/api/v1/projects
/api/v1/conversations
Later:
/api/v2/projects
Versioning should not be added everywhere without a reason.
The purpose is to provide controlled compatibility when breaking changes become necessary.
39.20 Idempotency
Some operations can accidentally be repeated.
For example:
Client sends request
↓
Network timeout
↓
Client does not know whether server succeeded
↓
Client retries
Without protection, a duplicate operation could occur.
For suitable operations, an idempotency key can be used:
Idempotency-Key: unique-operation-id
The server can associate the key with the operation result.
This is particularly valuable for operations involving external side effects.
39.21 Rate Limiting
APIs should not assume that every client sends requests at a reasonable rate.
Rate limiting can be applied at different levels:
IP
User
Session
API key
Endpoint
Resource
For example:
Authentication endpoint
↓
Strict rate limit
Normal read endpoint
↓
Moderate limit
Expensive AI operation
↓
Usage-based limit
The exact thresholds should be determined through capacity planning and abuse testing.
39.22 AI Operation Limits
AI generation requests may consume significantly more resources than ordinary database queries.
Therefore:
GET /projects
and
POST /ai/generate
should not necessarily share the same rate policy.
An AI request may require:
Authentication
+
Authorization
+
Usage quota
+
Rate limit
+
Input validation
+
Model policy
+
Budget check
39.23 Usage Accounting
The API layer should eventually connect AI requests to usage records.
Conceptually:
AI Request
│
├── User
├── Project
├── Model
├── Request ID
├── Input units
├── Output units
└── Status
This allows the system to monitor resource consumption.
39.24 API and AI Safety
AI endpoints require additional controls because the request is not necessarily a simple CRUD operation.
An AI request may involve:
User input
↓
Prompt construction
↓
Retrieved documents
↓
Memory
↓
Model
↓
Generated output
↓
Tool execution
Each stage can become a security boundary.
Therefore the API should not simply forward arbitrary user content to privileged systems.
39.25 Prompt and Context Boundary
A request such as:
"Use my documents to answer this question."
should result in controlled retrieval:
Authenticated User
↓
Authorized Project
↓
Authorized Documents
↓
Relevant Context
↓
Model
The API must not retrieve documents merely because their identifiers were supplied by the client.
39.26 Tool Invocation Boundary
Future agent APIs may request tools.
A safer model is:
Agent Request
↓
Tool Requested
↓
Permission Check
↓
Policy Check
↓
Optional Human Approval
↓
Tool Execution
The model itself should not bypass application authorization.
39.27 External Service Failures
The API may depend on:
Database
Object storage
Vector store
Model provider
Email service
Payment service
Queue
Any of these can fail.
The API should distinguish:
Client error
from:
Temporary infrastructure failure
For example:
503 Service Unavailable
may be appropriate when a required downstream service is temporarily unavailable.
39.28 Timeout Management
External requests should not be allowed to hang indefinitely.
Conceptually:
API Request
│
▼
External Service
│
├── Success
│
├── Failure
│
└── Timeout
Timeout behavior should be explicit.
Long-running AI tasks may be better represented as asynchronous jobs rather than holding an HTTP request open indefinitely.
39.29 Synchronous vs Asynchronous Operations
Simple operations:
Create project
Update profile
Read conversation
can generally be synchronous.
Long-running operations:
Large document processing
Video generation
Batch analysis
Embedding generation
Complex agent workflow
may be better represented as jobs.
Example:
POST /api/jobs
│
▼
Job Created
│
▼
202 Accepted
│
▼
Background Worker
│
▼
Job Complete
The client can then poll or receive a notification.
39.30 API Job Model
A future job record can contain:
id
userId
projectId
type
status
progress
input
result
error
createdAt
startedAt
completedAt
Example status lifecycle:
PENDING
↓
QUEUED
↓
RUNNING
↓
COMPLETED
Alternative path:
RUNNING
↓
FAILED
or:
RUNNING
↓
CANCELLED
39.31 Cancellation
Long-running operations should support cancellation where technically feasible.
Example:
POST /api/jobs/{id}/cancel
The server should verify:
Authenticated user
↓
Job ownership/access
↓
Job is cancellable
↓
Cancellation requested
39.32 Logging
API logging should provide enough information to diagnose failures.
Useful fields include:
requestId
timestamp
route
method
status
duration
user identifier
project identifier
error code
Avoid logging:
passwords
session tokens
API keys
private credentials
unnecessary personal data
full sensitive documents
39.33 Structured Logging
Instead of:
Something went wrong.
a structured log might conceptually contain:
{
"event": "api_request_failed",
"requestId": "req_example",
"route": "/api/projects",
"method": "POST",
"status": 500,
"errorCode": "DATABASE_ERROR"
}
Structured logs are easier to search and analyze.
39.34 Monitoring Metrics
Important API metrics include:
Request count
Error rate
Latency
95th percentile latency
99th percentile latency
Rate-limit events
Authentication failures
Database errors
External service failures
AI job failures
Monitoring should focus on operational signals rather than collecting unnecessary user content.
39.35 Security Events
Certain events should receive elevated attention:
Repeated authentication failures
Large numbers of authorization failures
Unusual API usage
Repeated invalid requests
Unexpected administrative operations
Abnormal agent/tool activity
These events can feed a security monitoring system.
39.36 API Testing
Every endpoint should have tests covering:
Happy path
Valid request
Authenticated user
Correct permissions
Expected result
Invalid input
Missing field
Wrong type
Too long
Invalid identifier
Authentication
No session
Expired session
Revoked session
Authorization
Wrong owner
Insufficient role
Forbidden operation
Reliability
Database unavailable
External service timeout
Duplicate request
Rate limit exceeded
39.37 Example API Test Matrix
| Scenario | Expected |
|---|---|
| Valid project creation | 201 |
| Missing project name | 400 |
| Anonymous project creation | 401 |
| Unauthorized project access | 403/404 according to policy |
| Duplicate operation | Controlled result |
| Excessive requests | 429 |
| Database unavailable | 503/500 according to failure policy |
39.38 API Security Checklist
[ ] Every protected endpoint authenticates the user
[ ] Authorization is checked separately
[ ] Ownership is verified
[ ] Request bodies are validated
[ ] Query parameters are validated
[ ] Client-supplied identity is not trusted
[ ] Sensitive errors are not exposed
[ ] Rate limiting exists where necessary
[ ] Expensive operations have usage controls
[ ] External requests have timeouts
[ ] Long-running work uses jobs where appropriate
[ ] Request IDs are available
[ ] Security events are logged
[ ] Secrets are excluded from logs
[ ] API contracts are documented
39.39 Complete Request Model
The complete architecture now becomes:
CLIENT
│
▼
HTTP REQUEST
│
▼
REQUEST ID
│
▼
VALIDATION
│
▼
AUTHENTICATION
│
▼
AUTHORIZATION
│
▼
RATE / USAGE
CONTROL
│
▼
SERVICE LAYER
│
┌────────────┴────────────┐
│ │
Repository External API
│ │
▼ ▼
PostgreSQL Model / Storage
│ │
└────────────┬────────────┘
│
▼
RESULT
│
▼
RESPONSE CONTRACT
│
▼
CLIENT
39.40 Relationship to Previous Chapters
The architecture now forms a continuous chain.
Chapter 34:
Database Architecture
Chapter 35:
Database Schema
Chapter 36:
Database Stack
Chapter 37:
Database Implementation
Chapter 38:
Authentication & Authorization
Chapter 39:
API Architecture
Together:
Data
↓
Database
↓
Identity
↓
Permission
↓
API
↓
Services
This is the foundation for the application-level AI systems that follow.
39.41 Conclusion
The API layer is not merely a collection of URLs.
It is a controlled boundary where:
- identity is established;
- permissions are evaluated;
- inputs are validated;
- business rules are applied;
- data is accessed;
- external services are coordinated;
- errors are classified;
- usage is measured;
- security events are observed.
The resulting principle is:
Never trust the request merely because
it reached the API.
The server must independently establish:
Who?
What?
Which resource?
Which permission?
Which operation?
Which limits?
Which policy?
Only after these checks should the operation execute.
The next chapter will build on this foundation with file and object-storage architecture, including secure uploads, metadata, ownership, processing pipelines, file validation, storage keys, signed access, lifecycle management, and the security boundaries required for AI document and media workflows.
END OF CHAPTER 39
Top comments (0)