API style choice — REST, gRPC, GraphQL — is less of a religious war than people make it. Each has a sweet spot. Picking by sweet spot avoids the 'we have gRPC because someone read a blog' anti-pattern.
REST — the default that mostly works
Resource-oriented, HTTP verbs, JSON. Universal client support, debuggable from curl, cache-friendly via HTTP. Right for: public APIs, third-party integrations, anywhere you don't control both ends. Versioning is awkward; over-fetching common.
gRPC — internal high-throughput
Protobuf + HTTP/2 + code-generated clients. Fast (binary, multiplexed). Strong typing across languages. Streaming support. Right for: internal service-to-service, performance-critical paths, polyglot orgs. Wrong for: browsers (gRPC-Web is workable but extra), public APIs (clients aren't ready).
GraphQL — client-driven queries
One endpoint; client specifies what to fetch. Eliminates over/under-fetching. Strong typing. Right for: complex client UIs (especially mobile with limited bandwidth) where queries vary across screens. Wrong for: simple CRUD (overkill), bandwidth-constrained backends (one big query can fan out to many internal calls), cache-friendly content (per-query caching is hard).
Versioning is the actual hard part
REST: header version, URL version, or 'we never break'. gRPC: protobuf field numbers + backward compatibility rules. GraphQL: schema deprecation. None of these is automatic — you must build for it from day 1 or pay later.
Error handling matters
HTTP status codes alone are insufficient (REST). gRPC has a status enum + details. GraphQL returns 200 with errors in body (controversial). Whatever you pick: distinguish 'client retriable', 'server retriable', and 'fatal'.