Design & Architecture

API & Contract Design

Intermediate

An API is a promise. Once a consumer depends on it, you cannot break it quietly. Design contracts to be clear, consistent, and hard to misuse, because the shape you ship is the shape you will support for years. A good API makes the right call obvious and the wrong call hard.

A contract is everything a caller can see: the routes, the request and response shapes, the status codes, the error format, and the rules about what is required and what each thing means. Design it from the consumer's side: predictable, consistent, and self-describing. Treat it as a stable surface that evolves carefully, not one that changes without warning.

APIs are also a security boundary. Every endpoint is an entry point an attacker will probe (see Trust Boundaries). So contract design and security design are the same task: validate input, authenticate and authorize, never return too much data, and never leak internals in errors. The goal is a clean contract that also fails safe.

Design for the consumer

Make the contract safe

Bind the entity, return it whole [HttpPost] public Customer Create(Customer c) { db.Insert(c); return c; }

This allows mass-assignment: a caller can set Id, TenantId, Role, or KycStatus directly. The response also serialises every column, including internal and sensitive fields. The contract becomes whatever the table happens to be.

Explicit request and response DTOs [HttpPost] public CustomerResponse Create(CreateCustomerRequest req) {
var c = new Customer { TenantId = User.GetTenantId(), Name = req.Name };
db.Insert(c);
return CustomerResponse.From(c); // only safe, intended fields
}

The caller can set only what the request DTO allows. Privileged values come from the server. The response exposes a deliberate, stable shape.

Self-review checklist

Why it matters: APIs are long-lived promises and a front-line attack surface at the same time. A consistent, consumer-focused contract is cheap to use and safe to evolve. A leaky or inconsistent one breaks integrations, exposes data, and locks in internal details you can never take back. Design it once, on purpose, because you will live with it.