One order, id 42, status shipped, total 12999 cents, drawn byte by byte: 19 bytes as gRPC, 48 as REST JSON, 68 as GraphQL JSON and 270 as a SOAP 1.2 envelope. The value bytes are highlighted against the overhead each format adds.

API types and architectures: what REST, GraphQL, gRPC, SOAP, webhooks and event streams cost to run and to change

Most lists of API styles end in an analogy. Put one order lookup through all of them instead, count the bytes, read each spec for caching and change rules, and the right style for each part of your system becomes a cost decision.

What are API types and architectures?#

API types sort interfaces by who may call them (public, partner, internal or composite), while architecture styles such as REST, GraphQL, gRPC, SOAP, webhooks and event streams decide how calls travel. So what is API architecture? Among API types and architectures, it is the pair of decisions behind every interface you run: who may call it, and what shape the call takes on the wire.

Because the two axes are independent, real API architectures combine them freely. For example, a public API can be REST or GraphQL. An internal API can be gRPC or a Kafka topic. Stoplight's guide to API types sorts them by "who has access", and that is the audience axis. Meanwhile, a specification sets the style axis. The owners are RFC 9110 for HTTP semantics, the GraphQL spec and grpc.io. Then come the W3C SOAP 1.2 Recommendation, the Standard Webhooks spec, RFC 6455 for WebSockets and the Apache Kafka docs.

API protocols and architectures are often listed together, but they are not the same thing. A protocol such as HTTP/2 carries bytes. Instead, a style decides what those bytes mean. Then the API formats on the wire (JSON, XML or binary Protocol Buffers) follow from the style.

So what is API based architecture in practice? It is a system whose parts talk only through these contracts. In short, every boundary in that system gets its own type and its own style. The different types of API architecture are tools for different boundaries.

Two decisions per interfaceEvery interface is two decisions: who may call it (the type) and how the call travels (the style), with the wire format following from the style. Source: Atyantik diagram from RFC 9110, the GraphQL spec, grpc.io and the W3C SOAP 1.2 Recommendation, read 6 October 2026.

What does the choice of API style cost you?#

The style you pick sets three running costs for as long as the API lives: bytes per call, how much traffic a cache can absorb, and what a breaking change costs. Choosing the style per boundary keeps each of those costs where it is cheapest to pay.

Bytes come first. In our 6 October 2026 test, we encoded one order: id 42, status shipped, total 12999 cents. It took 19 bytes as a gRPC message, 48 as REST JSON, 68 as a GraphQL response and 270 as a SOAP 1.2 envelope. Those counts exclude headers and compression.

Take an illustrative load of 10,000 lookups a second. At that rate, the 222-byte gap from our 6 October 2026 test between SOAP and REST adds 2.22 megabytes a second. That comes to about 192 gigabytes a day. The 29-byte gap from the same 6 October 2026 test between REST and gRPC adds about 25 gigabytes a day at that illustrative rate. Meanwhile, at 10 lookups a second both gaps are tiny.

What a style's bytes cost at your own call rate

Set your call rate and two per-message sizes from the 6 October 2026 test; it computes the extra bytes per call, a second and a day.

Your traffic

measured on this projectan assumption, change it

Bytes per message, from the 6 October 2026 test

Message bodies only; headers and compression are excluded, and compression narrows the text formats.

Extra gigabytes a day

191.8

Extra bytes per call
222
Extra bytes a second
2,220,000

The call rate is an illustrative default, not a measured load. Change it to your own.

Other costs work alike. First, a read that a content delivery network (CDN) can serve never reaches your servers. Second, a field you can retire without a new version never forces a client release. Finally, a push style adds a fourth line. On the Standard Webhooks example schedule, read on 6 October 2026, one failed delivery is retried for about 75 hours.

What are the four types of API?#

The four types of API are public, partner, internal and composite, and each one fixes who holds credentials, how slowly the contract may change and how much documentation it needs. Each audience maps to a boundary with its own change speed, and that is what narrows the style choice.

Stoplight's guide describes public APIs as "available for anyone to use with little to no restriction". Most still ask for a key. Because you cannot reach every caller, a public contract changes slowest. It also needs the fullest documentation. Partner APIs, in Stoplight's words, "exist somewhere between public and private APIs". They serve a named company for a set purpose. Therefore, you can email every caller before a change and agree the contract with each one.

In contrast, internal APIs serve a closed group, usually your own teams. Since you ship both sides, they can change fastest. They can also use a binary style that a browser would struggle with. Finally, Stoplight defines a composite API as one "that lets you hit multiple API endpoints on a single call". So it is a boundary of its own, often in front of a browser or mobile app.

In short, the answer to "what are the 4 types of API" is four boundaries. The types of APIs are boundaries, and that is how API types and architectures connect. Then name the boundary each of your own interfaces sits on before you pick a style for it.

The four types of API by who calls them and what that means for change. Source: Stoplight's guide to API types, read 6 October 2026.

TypeWho calls itWhat that means for change
PublicAnyone, with little to no restriction; most still ask for a keyChanges slowest and needs the fullest documentation, because you cannot reach every caller
PartnerA named company for a set purposeYou can email every caller before a change and agree the contract with each one
InternalA closed group, usually your own teamsChanges fastest, since you ship both sides; a binary style is an option
CompositeOne call that hits multiple endpointsA boundary of its own, often in front of a browser or mobile app

What does a minimal request look like in each style?#

The same order lookup is a GET in REST, a POST with a query document in GraphQL, a typed method call in gRPC and an XML envelope in SOAP 1.2. Seeing one lookup written four ways shows where each style puts the operation, the types and the metadata.

bash
# 1. REST: a GET on the resource, cacheable under RFC 9110
curl -s https://api.example.com/orders/42 -H 'Accept: application/json'

# 2. GraphQL: a POST carrying the query document and its variables
curl -s https://api.example.com/graphql \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/graphql-response+json' \
  -d '{"query":"query($id:ID!){order(id:$id){id status totalCents}}","variables":{"id":"42"}}'

# 3. SOAP 1.2: a POST carrying an XML envelope in the SOAP namespace
curl -s https://api.example.com/soap \
  -H 'Content-Type: application/soap+xml; charset=utf-8' \
  -d '<?xml version="1.0"?><env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"><env:Body><m:GetOrder xmlns:m="urn:example:orders"><m:id>42</m:id></m:GetOrder></env:Body></env:Envelope>'

# 4. gRPC: the contract is a .proto file; generated stubs make the call
cat > orders.proto <<'PROTO'
syntax = "proto3";
service OrderService {
  rpc GetOrder (GetOrderRequest) returns (Order);
}
message GetOrderRequest { int64 id = 1; }
message Order { int64 id = 1; string status = 2; int64 total_cents = 3; }
PROTO

REST puts the operation in the method and the noun in the URL. However, GraphQL puts both in a document inside the body. Then SOAP wraps the call in an envelope. The W3C SOAP 1.2 Recommendation of 2007 fixes its namespace as http://www.w3.org/2003/05/soap-envelope. In gRPC, the call is a method on a service. The grpc.io introduction says the client "has a stub" generated from that file. Also, on the wire, gRPC sends application/grpc over HTTP/2, per the gRPC protocol document.

A webhook is the REST request turned around. Your server POSTs a JSON event to a URL the partner gave you. Meanwhile, a WebSocket or a Kafka topic carries the same event as a message, with no request at all.

Which specs should you build each style from?#

Build each style from its own specification: RFC 9110 for REST, the GraphQL spec and its HTTP guidance, grpc.io with the protobuf encoding guide, and W3C SOAP 1.2. The list below was checked on 6 October 2026. Because every style has an owning document, building from it avoids the drift that tutorials bring in.

  1. RFC 9110, IETF, June 2022: methods, status codes and which responses a cache may reuse.
  2. GraphQL serving over HTTP: POST and GET handling and the response media type.
  3. GraphQL spec, October 2021: the schema language and field deprecation.
  4. gRPC core concepts: the service definition, deadlines and metadata.
  5. Protocol Buffers encoding: how field numbers and varints become bytes.
  6. gRPC over HTTP/2: the content type and the length prefix on each message.
  7. Standard Webhooks: the headers, signatures and retry schedule.
  8. Apache Kafka introduction: topics, partitions and how long events are kept.
Who owns each styleWhich document owns each API style, checked 6 October 2026. Source: the specifications linked in this section.

How many bytes does the same answer cost in each format?#

In our 6 October 2026 test of one order lookup, a gRPC message took 19 bytes, REST JSON 48, GraphQL JSON 68 and a SOAP 1.2 envelope 270, before headers or compression. Binary Protocol Buffers are smallest because field numbers replace field names, and SOAP is largest because the envelope and namespaces repeat on every call.

Show data table
Bytes for one order lookup by style, first-party test, Atyantik, 6 October 2026; message body only, no headers, no compression.
Item Value
gRPC (Protocol Buffers + 5-byte prefix) 19 bytes
REST JSON 48 bytes
GraphQL JSON 68 bytes
WebSocket frame (JSON event) 77 bytes
Webhook event (JSON) 115 bytes
SOAP 1.2 envelope 270 bytes

The same order costs 19 bytes as gRPC and 270 as a SOAP 1.2 envelope.

Bytes per order lookup Bytes for one order lookup by style, first-party test, Atyantik, 6 October 2026; message body only, no headers, no compression. first-party measurement, Atyantik, 6 October 2026: the same order (id 42, status shipped, total_cents 12999) encoded per each spec

The protobuf encoding guide explains the gap. There, each field is a record "consisting of the field number, a wire type and a payload". So the name total_cents never travels. In our 6 October 2026 test, the 14-byte message plus the 5-byte prefix from the gRPC protocol document makes 19. Meanwhile, REST JSON spells every key on every call, and GraphQL adds the data and order wrappers. Finally, SOAP carries an XML declaration, an envelope, a body and two namespaces. Only then does it say anything about the order.

However, these are per-message numbers. Compression narrows the text formats, while HTTP headers often outweigh a small body. So the ratio matters at volume, on chatty internal links and on mobile data. It barely matters on a dashboard called a few times a minute.

Which API styles can an HTTP cache serve without extra work?#

RFC 9110 (June 2022) lets a cache reuse GET responses with codes such as 200 and 404 (Not Found) by default, so REST reads cache freely while POST-based GraphQL, gRPC and SOAP calls do not. Requests sent as GET with cacheable status codes get CDN savings by default, and GraphQL regains them only with GET queries and persisted documents.

RFC 9110, published by the IETF in June 2022, lists the statuses a cache may reuse without explicit freshness headers. It closes the list plainly: "all other status codes are not heuristically cacheable". As a result, a public REST read can be served from the edge with a Cache-Control header and no code. Because a cache keys on the method and the URL, a POST body is invisible to it.

The GraphQL Foundation's guide says a server "must handle the HTTP POST method for query and mutation operations, and may also accept the GET method for query operations". Its performance page adds that GET requests "are typically considered cacheable by default". But long queries hit URL size limits. So the same page points to persisted queries, where the client sends "a hash of the query instead" and the server looks the document up. In practice, a cacheable GraphQL API is a project, not a default.

gRPC and SOAP sit further out. Since both send every call as a POST, a CDN passes them through untouched. Instead, their caching lives in your application, which is extra code and extra memory to run. Therefore, a read-heavy public API leans toward REST on caching alone.

What a cache can serveWhich calls an HTTP cache or CDN can reuse: a GET with a heuristically cacheable status, under RFC 9110 (June 2022); POST-based GraphQL, gRPC and SOAP pass through to your servers. Source: IETF RFC 9110, June 2022; GraphQL Foundation serving over HTTP and performance guides, read 6 October 2026.

How does each contract change without breaking clients?#

REST versions through URLs or media types, GraphQL deprecates fields in place, Protocol Buffers let old readers skip unknown field numbers, and SOAP changes the XML namespace. The cheapest contract to change is the one whose old clients keep working without a new version, which is GraphQL deprecation and protobuf field numbers.

The GraphQL spec lets a schema mark a field @deprecated. It then says "It is still legal to include these fields in a selection set (to ensure existing clients are not broken by the change)". So a field can be retired slowly while old clients keep running. Similarly, Protocol Buffers identify fields by number. The encoding guide describes an old server that "treats some fields as unknown" and still serializes the message. As a result, you can add a field without a release on the other side. The one rule is never to reuse a number.

REST has no built-in rule for this. Instead, teams put a version in the path. Others use content negotiation from RFC 9110 (June 2022), where the Accept header asks for a media type. Either way, a breaking change means two versions running until the last client moves. Meanwhile, SOAP ties the message to its namespace. So a new contract usually means a new namespace that every partner must adopt.

How each style changes its contract, and whether old clients keep working without a new version. Source: the GraphQL spec (October 2021), the Protocol Buffers encoding guide, IETF RFC 9110 (June 2022) and the W3C SOAP 1.2 Recommendation.

StyleHow the contract changesOld clients keep working without a new version?
GraphQLMark a field @deprecated and retire it slowlyYes: deprecated fields stay legal in a selection set
gRPC (Protocol Buffers)Add fields under new numbers; never reuse a numberYes: old readers treat new fields as unknown
RESTA version in the path, or a media type through the Accept headerNo: two versions run until the last client moves
SOAPA new XML namespaceNo: every partner must adopt the new namespace

How long does a failed webhook keep costing you?#

On the example schedule in the Standard Webhooks specification, read on 6 October 2026, a failed delivery keeps being retried for about 75 hours, so every receiver must accept duplicates by event id. A webhook is cheap to send but obliges both sides to run retries, signatures and idempotency for days after a failure.

Show data table
When each retry fires on the Standard Webhooks example schedule; arithmetic from the spec's listed delays, read 6 October 2026.
Stage Value
attempt 4 0.58 hours
attempt 5 2.58 hours
attempt 6 7.58 hours
attempt 7 17.58 hours
attempt 8 31.58 hours
attempt 9 51.58 hours
attempt 10 75.58 hours

The tenth attempt lands about 75 hours after the first.

Hours after the first attempt When each retry fires on the Standard Webhooks example schedule; arithmetic from the spec's listed delays, read 6 October 2026. Standard Webhooks specification, example retry schedule, accessed 6 October 2026

The spec is direct: "It's recommended to retry delivery following a retry schedule spanning multiple days, with an exponential backoff." Its example, read on 6 October 2026, waits 5 seconds, then 5 minutes, then 30 minutes, then hours. So the tenth attempt lands 75 hours and 35 minutes after the first. Meanwhile, the sender keeps a queue for that whole window. In turn, the receiver must check a signature in the webhook-signature header. It must also use the webhook-id header "as an idempotency key", because the same event can arrive twice.

In short, a webhook moves cost to both companies. That suits a partner boundary, where you cannot hold a connection open to someone else's servers. However, it is a poor fit inside your own system. There, an event log does the same job with replay built in.

What does an open connection or an event log cost to run?#

Under RFC 6455 (December 2011), a WebSocket frame adds only 6 to 14 bytes, but each client holds a connection open, while Apache Kafka keeps every event durably and lets consumers replay it. Streaming styles move the cost from bytes per message to infrastructure held open: connections for WebSockets, a replicated log for Kafka.

Show data table
Header bytes on a client-to-server WebSocket frame, arithmetic from IETF RFC 6455 section 5.2, December 2011.
Item Value
payload up to 125 bytes 6 bytes
payload 126 to 65,535 bytes 8 bytes
payload over 65,535 bytes 14 bytes

A client frame adds 6 to 14 header bytes, depending on the payload size.

WebSocket client frame header Header bytes on a client-to-server WebSocket frame, arithmetic from IETF RFC 6455 section 5.2, December 2011. IETF RFC 6455, section 5.2, December 2011

RFC 6455 (IETF, December 2011) says "The WebSocket Protocol enables two-way communication between a client" and a server over one connection. Each client frame carries a 2-byte header and a 32-bit masking key, plus extra length bytes for larger payloads. That is cheap per message. Instead, the cost is the open socket, because a server holds one per connected browser. Similarly, the gRPC performance guide notes that each HTTP/2 connection "usually has a limit on the number of concurrent streams".

Kafka trades sockets for a log. Its introduction names one goal plainly: "To store streams of events durably and reliably for as long as you want." Events "can be read as often as needed", and each topic sets its own retention. Also, topics are split into partitions across brokers and can be replicated. So that is a cluster to run, watch and upgrade. In return, a new consumer can replay last week's orders without asking anyone to resend them.

How do you choose an API style for each boundary of your system?#

Pick the style per boundary: REST for public reads that should cache, GraphQL for one team's varied clients, gRPC between internal services, webhooks to partners and an event log inside. A boundary's audience, cache need and team skills decide its style, and one system usually ends up with two or three styles.

Which API style fits each boundary, why, and the skills the team needs. Source: the specs and docs cited above; byte figures from our 6 October 2026 test.

BoundaryStyleWhy, from the costs aboveSkills the team needs
Public read APIRESTGET responses cache by default (RFC 9110)HTTP semantics, cache headers, versioning
One team's web and mobile clientsGraphQLClients pick fields; deprecation keeps old ones workingSchema design, resolvers, persisted queries
Internal service to servicegRPCSmallest message in our test; typed stubsProtocol Buffers, generated stubs, deadlines
Push to a partnerWebhooksNo open connection to another companyRetries, signatures, idempotency keys
Browser live updatesWebSocket6 to 14 header bytes per frameConnection limits, reconnect logic
Internal events many teams readKafka topicDurable, replayable logRunning a broker cluster, partitions, retention
Existing enterprise partner on SOAPSOAP 1.2The partner already runs itXML envelopes, namespaces, faults

Source: the specs and docs cited above; byte figures from our 6 October 2026 test.

Then check the team. The grpc.io introduction expects you to define services in a .proto file and generate code. So someone must own those files. Likewise, a GraphQL schema needs an owner for deprecations. Also, webhooks need someone on call for the retry queue. When a style has no owner, its costs land on whoever is nearest.

For example, a shop might run REST for its public catalogue and GraphQL behind its own app. It might add gRPC between checkout and stock, webhooks to its carriers and a Kafka topic for orders. That is five styles, each where it is cheapest.

One shop, five stylesOne illustrative shop with five API styles, each at the boundary where it is cheapest. Illustrative example, from the boundary table above.

When is adding a new API style the wrong fit?#

Adding a second style is the wrong fit when one client calls the API, the traffic is small, or nobody on the team will own the schema, the proto files or the retry queue. Keep the style you already run unless a measured cost is large enough to pay for a second toolchain. That cost shows up as bytes, cache misses or retries.

The 29-byte gap from our 6 October 2026 test between gRPC and REST is real. At an illustrative 10 calls a second, though, it comes to 290 bytes a second. In that case, the better tool is the REST API you have, with compression switched on. Likewise, take a single web app as the only client. There, a GraphQL layer adds a schema and resolvers to solve a problem you do not have. Plain REST endpoints shaped for that app are simpler.

Also, if you only need to tell a browser that something changed, a held-open WebSocket may be more than you need. For example, our web push guide shows a push channel with no socket to keep open. Finally, if a partner already runs SOAP, keep SOAP at that boundary rather than asking them to rebuild.

29 B10 /s290 B/s

Where should you go from here?#

If you are splitting a monolith, decide the service boundaries first, then give each one the style its audience needs. Because the boundary decision comes before the style, the next reads start there.

Start with when a module should become a service, then how teams split ownership of a front end. For a REST integration in practice, see cart updates with payment intents. If you want help designing or connecting an API, our API integration and custom software development teams do that work. Still, the specs linked above are enough on their own to make every choice here.

Questions this post answers

What are the four types of API?
The four types of API are public, partner, internal and composite, and each one fixes who holds credentials, how slowly the contract may change and how much documentation it needs.
What is API architecture?
Among API types and architectures, it is the pair of decisions behind every interface you run: who may call it, and what shape the call takes on the wire.
How do you choose between API types and architectures for each boundary?
Pick the style per boundary: REST for public reads that should cache, GraphQL for one team's varied clients, gRPC between internal services, webhooks to partners and an event log inside.

Keep reading