An API that mirrors database collections can make simple CRUD easy, but business work often has richer meaning. Publishing a post, approving an invoice, and inviting a user are operations with permissions, state rules, and side effects. Designing an API around those operations gives clients a clearer contract and keeps important decisions on the server. It does not mean abandoning ordinary resource routes; it means using them where they fit and naming actions where the domain requires them.
Identify the domain action
Start from the user outcome. “Approve an invoice” is more precise than “update invoice status to approved,” because approval may require a role, a valid current state, a timestamp, and an audit event. The request should supply the information the operation needs, while the server determines derived values. Avoid exposing internal fields that let a client skip a transition by writing arbitrary state. A part-first route hierarchy can make ownership clear, with actions under the part and type that own the rule. The path should describe the capability rather than the current database layout.
Keep reads render-ready
Clients should receive ordered, filtered, normalized data with counts and visibility flags calculated by the API. A panel should not decide who may approve based on raw role strings or infer a total from incomplete items. The API can expose a clear list and detail contract for the business resource. Pagination and sorting should be deterministic, and tenant scope should be enforced before returning data. A stable read model can differ from the write model when the interface needs summaries or embedded cross-part information.
Make writes safe to repeat
Networks fail after the server completes an action but before the client receives the response. A retry should not send two invoices or create two payments. Use idempotency keys or state checks for operations with harmful duplicate effects. Define what happens when the same command is repeated, when the resource changed since the client loaded it, and when an external provider is unavailable. Return a result the client can render without guessing whether the action succeeded. Keep error keys consistent and localizable.
Enforce authorization and validation
The business operation should check the actor, tenant, target resource, and permitted transition. Validate input at the API boundary, then enforce domain invariants near the write. A visible button is not a permission check. Background jobs and other clients may call the same operation, so centralize the rule rather than duplicating it in one route. Log significant state changes with actor and outcome. If the operation spans several parts, define which part owns the decision and how other parts receive the resulting data.
Evolve the contract carefully
A database schema can change while the public operation remains stable. Conversely, a business policy may change even if storage does not. Document observable behavior and test important clients against it. Add optional fields and new operations before removing old ones where possible. Avoid exposing an endpoint for every internal helper; the public surface should match capabilities customers and clients actually need. Usage data can show whether an old action is still called before deprecation.
Keep the design proportionate
Not every create or edit needs a special command route. Use conventional resource operations for straightforward data changes, and explicit domain actions for transitions with meaningful rules. A small team benefits from a contract that tells developers where the business decision lives and gives operators a way to understand failures. The strongest API makes the correct workflow easy to call and the invalid shortcut impossible to express. A client should not need to know the order of several private database edits to complete one customer action. The server can coordinate those steps and return a coherent result, while exposing failure states that an operator can diagnose.