API Design
itSoftware engineering
API Design
An application programming interface, or API, is a contract between a provider and a consumer. The contract covers more than syntax. It defines available capabilities, data meanings, valid operations, failure behavior, and change expectations.
Good API design lets a consumer predict what will happen without knowing the provider's internals. Start with consumer jobs and domain concepts. Then choose an interaction style and description format. An elegant URL cannot repair an unclear model.
Start with the contract
Identify consumers, their goals, and the resources or operations they need. Give important concepts stable identities and names. Define relationships, ownership, state transitions, invariants, and authorization boundaries before choosing field names.
A public model should not mirror a database schema by accident. Storage structures optimize persistence. API structures express a durable consumer contract. Coupling the two makes internal changes visible to consumers.
Use consistent patterns for similar operations. A resource-oriented API usually exposes collections and resources through a small set of standard operations. Custom operations still belong when the domain action does not fit create, retrieve, update, delete, or list behavior.
Choose an interaction style
HTTP resource APIs fit broad interoperability, request and response exchanges, caching, and standard intermediary behavior. GraphQL exposes a typed schema with query, mutation, and optional subscription root operations. It fits consumers that need to select connected data from one graph.
Remote procedure call APIs organize the contract around service methods. gRPC supports unary calls plus server, client, and bidirectional streaming. Message-driven APIs exchange messages through channels and decouple the sender's timing from the receiver's processing.
Choose from consumer needs and operating constraints. Consider browser support, streaming, latency, tooling, network boundaries, message delivery, and team skills. One system may expose more than one style at deliberate boundaries.
Use HTTP semantics as designed
For an HTTP API, the method expresses request intent. Safe methods have read-only requested semantics. Idempotent methods can be repeated with the same intended server effect. These properties guide retry behavior and intermediary handling.
Status codes communicate generic outcomes. For example, 201 Created reports creation, while 202 Accepted reports accepted but incomplete processing. A long-running action needs an operation or status resource because HTTP cannot send a later status code for the original response.
Represent concurrent state explicitly. Entity tags and If-Match preconditions can prevent one client from silently overwriting another client's change. Define duplicate-request behavior as well. A timeout leaves the client uncertain about whether the server applied the request.
Design data around use
Requests should contain what the operation needs. Responses should contain stable data consumers can rely on. Define required and optional fields, null behavior, formats, units, constraints, and defaults. Examples help readers, but schemas make constraints testable.
Lists need an explicit ordering and pagination contract. Opaque continuation tokens keep internal cursor details out of the public surface. Filtering and sorting syntax also become part of the contract, so define their supported fields and error behavior.
OpenAPI provides a language-independent description for HTTP APIs. AsyncAPI describes message-driven APIs. A machine-readable description supports documentation, validation, testing, and code generation, but it cannot decide whether the domain model makes sense.
Make failure usable
Errors are normal API results. Distinguish invalid input, missing authentication, denied authorization, missing resources, conflicts, exhausted limits, and provider failures. Preserve protocol meaning instead of returning one generic success or server-error response.
RFC 9457 problem details define a reusable error format for HTTP APIs. A stable problem type identifies the category. The response can also include a title, status, occurrence identifier, and extensions. Do not expose stack traces, secrets, or implementation details.
Evolve with evidence
Consumers deploy code on their own schedules. Compatibility therefore includes behavior, not only field presence. Removing a field is breaking. Tightening validation, changing a default, or adding an enum value may also break consumers.
Prefer additive changes, stable identifiers, tolerant handling of unknown response fields, and explicit deprecation. RFC 9745 defines an HTTP Deprecation response header and a deprecation link relation. Deprecation signals future change; it does not itself change current behavior.
Treat the contract as a product. Review it with consumers, test examples and schemas, observe real usage, publish change policy, and retire behavior only through a communicated migration path.
Limits
No interface style removes distributed-system failure. A schema cannot guarantee authorization, transactional behavior, delivery, or compatibility. Generated clients reduce mechanical work, but they also spread contract mistakes quickly.
API design is also not implementation design. Consumers need stable behavior and useful diagnostics. They usually do not need internal tables, queues, class names, or deployment topology.
