DEV Community

Cover image for ACAI — Chapter 39: API Architecture, Request Processing & Secure Service Boundaries
Black Shadow Team ©
Black Shadow Team ©

Posted on

ACAI — Chapter 39: API Architecture, Request Processing & Secure Service Boundaries

#ai

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Individual resources can use:

/api/projects/{projectId}
/api/conversations/{conversationId}
/api/files/{fileId}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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_..."
Enter fullscreen mode Exit fullscreen mode

The request ID can appear in server logs.

A simplified flow:

Client Request
      │
      ▼
Generate Request ID
      │
      ├──────────────► Application Logs
      │
      ▼
Process Request
      │
      ▼
Response
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

The server should verify:

name exists
name is a string
name length is acceptable
name does not violate application rules
Enter fullscreen mode Exit fullscreen mode

Validation should happen before business logic.


39.9 Validation Layer

A reusable validation module can be created:

src/lib/validation.ts
Enter fullscreen mode Exit fullscreen mode

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(),
  });
Enter fullscreen mode Exit fullscreen mode

The API can then validate:

const body =
  createProjectSchema.parse(
    await request.json()
  );
Enter fullscreen mode Exit fullscreen mode

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?"
Enter fullscreen mode Exit fullscreen mode

Every output context must be handled appropriately.


39.11 Authentication Middleware

Protected routes should resolve the current user.

Conceptually:

const user = await requireUser();
Enter fullscreen mode Exit fullscreen mode

If no authenticated identity exists:

401 Unauthorized
Enter fullscreen mode Exit fullscreen mode

The route should stop processing.


39.12 Authorization Middleware

Authentication does not guarantee access.

For project operations:

const project =
  await getUserProject(
    user.id,
    projectId
  );
Enter fullscreen mode Exit fullscreen mode

If the user does not own or otherwise have permission to access the project:

403 Forbidden
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Error response:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request."
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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,
      }
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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,
    },
  });
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Example:

export async function
createProjectRecord(data: {
  name: string;
  description?: string;
  ownerId: string;
}) {
  return prisma.project.create({
    data,
  });
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Later:

/api/v2/projects
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Without protection, a duplicate operation could occur.

For suitable operations, an idempotency key can be used:

Idempotency-Key: unique-operation-id
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

For example:

Authentication endpoint
      ↓
Strict rate limit

Normal read endpoint
      ↓
Moderate limit

Expensive AI operation
      ↓
Usage-based limit
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

and

POST /ai/generate
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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."
Enter fullscreen mode Exit fullscreen mode

should result in controlled retrieval:

Authenticated User
       ↓
Authorized Project
       ↓
Authorized Documents
       ↓
Relevant Context
       ↓
Model
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Any of these can fail.

The API should distinguish:

Client error
Enter fullscreen mode Exit fullscreen mode

from:

Temporary infrastructure failure
Enter fullscreen mode Exit fullscreen mode

For example:

503 Service Unavailable
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

can generally be synchronous.

Long-running operations:

Large document processing
Video generation
Batch analysis
Embedding generation
Complex agent workflow
Enter fullscreen mode Exit fullscreen mode

may be better represented as jobs.

Example:

POST /api/jobs
        │
        ▼
Job Created
        │
        ▼
202 Accepted
        │
        ▼
Background Worker
        │
        ▼
Job Complete
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Example status lifecycle:

PENDING
   ↓
QUEUED
   ↓
RUNNING
   ↓
COMPLETED
Enter fullscreen mode Exit fullscreen mode

Alternative path:

RUNNING
   ↓
FAILED
Enter fullscreen mode Exit fullscreen mode

or:

RUNNING
   ↓
CANCELLED
Enter fullscreen mode Exit fullscreen mode

39.31 Cancellation

Long-running operations should support cancellation where technically feasible.

Example:

POST /api/jobs/{id}/cancel
Enter fullscreen mode Exit fullscreen mode

The server should verify:

Authenticated user
        ↓
Job ownership/access
        ↓
Job is cancellable
        ↓
Cancellation requested
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Avoid logging:

passwords
session tokens
API keys
private credentials
unnecessary personal data
full sensitive documents
Enter fullscreen mode Exit fullscreen mode

39.33 Structured Logging

Instead of:

Something went wrong.
Enter fullscreen mode Exit fullscreen mode

a structured log might conceptually contain:

{
  "event": "api_request_failed",
  "requestId": "req_example",
  "route": "/api/projects",
  "method": "POST",
  "status": 500,
  "errorCode": "DATABASE_ERROR"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Invalid input

Missing field
Wrong type
Too long
Invalid identifier
Enter fullscreen mode Exit fullscreen mode

Authentication

No session
Expired session
Revoked session
Enter fullscreen mode Exit fullscreen mode

Authorization

Wrong owner
Insufficient role
Forbidden operation
Enter fullscreen mode Exit fullscreen mode

Reliability

Database unavailable
External service timeout
Duplicate request
Rate limit exceeded
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

39.40 Relationship to Previous Chapters

The architecture now forms a continuous chain.

Chapter 34:

Database Architecture
Enter fullscreen mode Exit fullscreen mode

Chapter 35:

Database Schema
Enter fullscreen mode Exit fullscreen mode

Chapter 36:

Database Stack
Enter fullscreen mode Exit fullscreen mode

Chapter 37:

Database Implementation
Enter fullscreen mode Exit fullscreen mode

Chapter 38:

Authentication & Authorization
Enter fullscreen mode Exit fullscreen mode

Chapter 39:

API Architecture
Enter fullscreen mode Exit fullscreen mode

Together:

Data
 ↓
Database
 ↓
Identity
 ↓
Permission
 ↓
API
 ↓
Services
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

The server must independently establish:

Who?
What?
Which resource?
Which permission?
Which operation?
Which limits?
Which policy?
Enter fullscreen mode Exit fullscreen mode

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)