API Types
A comprehensive guide to API types — covering REST, RPC/gRPC, GraphQL, event-driven, SOAP, WebSocket, and webhook APIs. Learn what each type solves, how it works, when to choose it, and how to recognise which type a real-world integration requires.
API Types
Not all APIs look like GET /orders. The choice of API type is one of the most consequential decisions in system design — it determines the communication model, latency characteristics, contract shape, tooling ecosystem, and how both producers and consumers must be built.
This article covers the seven primary API types used in production systems:
| Type | Communication Model | Protocol | Primary Use Case |
|---|---|---|---|
| REST | Request / Response | HTTP/1.1, HTTP/2 | Web, mobile, public APIs, CRUD |
| RPC / gRPC | Request / Response | HTTP/2 (gRPC) | Service-to-service, streaming, low latency |
| GraphQL | Request / Response | HTTP | Flexible querying, multi-client, BFF |
| Event-Driven | Publish / Subscribe | Kafka, AMQP, SNS | Async workflows, high throughput, decoupling |
| WebSocket | Bidirectional persistent | HTTP upgrade + WS | Real-time: chat, feeds, live dashboards |
| Webhook | Server push (HTTP POST) | HTTP | Notifications, integrations, event delivery |
| SOAP | Request / Response | HTTP, SMTP, JMS | Enterprise, regulated, legacy integrations |
REST (Representational State Transfer)
REST is an architectural style — not a protocol — that uses HTTP as a transport and models everything as resources that can be created, read, updated, or deleted.
How It Works
A REST API exposes resources at URL paths. The HTTP method encodes the intent:
GET /v1/accounts/{id} → read an account
POST /v1/accounts → create an account
PUT /v1/accounts/{id} → replace an account
PATCH /v1/accounts/{id} → partially update an account
DELETE /v1/accounts/{id} → delete an account
Requests and responses are typically JSON over HTTPS. The server is stateless — each request contains all the information needed to process it; the server holds no session state between calls.
Example Interaction
Request:
GET /v1/accounts/acc_7f3a9b2c HTTP/1.1
Host: api.bank.com
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
Accept: application/json
Response:
{
"accountId": "acc_7f3a9b2c",
"type": "CHECKING",
"status": "ACTIVE",
"currency": "USD",
"availableBalance": 4250.00,
"owner": {
"customerId": "cust_a1b2c3",
"name": "Jane Smith"
},
"createdAt": "2024-03-15T09:00:00Z"
}
REST Constraints
A true REST API satisfies six architectural constraints:
- Client-server — UI and data storage are separated
- Stateless — no client session state on the server
- Cacheable — responses declare whether they can be cached
- Uniform interface — consistent resource addressing, self-descriptive messages
- Layered system — client cannot tell if it's talking to the origin server or a proxy
- Code on demand (optional) — servers can extend client functionality by sending executable code
Most "REST APIs" in practice satisfy the first four. The uniform interface is the most important in practice.
When to Use REST
✅ Public APIs exposed to external developers
✅ Mobile and web client-to-server communication
✅ CRUD-heavy services with clear resource models
✅ Integrations where consumers are unknown and diverse
✅ Services where HTTP caching provides value
✅ Any API that must be human-navigable through a browser
When REST Is Not the Right Choice
❌ Real-time bidirectional communication (use WebSocket)
❌ Service-to-service calls where latency < 10 ms matters (consider gRPC)
❌ Clients that need exactly the fields they specify — no more (consider GraphQL)
❌ High-throughput event streaming (use an event-driven approach)
RPC / gRPC (Remote Procedure Call)
RPC models API interactions as function calls rather than resource operations. Instead of GET /orders/123, you call GetOrder(orderId: "123"). The most widely used modern RPC protocol is gRPC, developed by Google.
How gRPC Works
gRPC uses three technologies together:
- Protocol Buffers (protobuf) — a language-neutral binary serialization format that defines the service contract in
.protofiles - HTTP/2 — the transport, which supports multiplexing, header compression, and bidirectional streaming
- Code generation — the
.protodefinition generates client and server stubs in any supported language
Service definition (.proto file):
syntax = "proto3";
package banking.v1;
service AccountService {
rpc GetAccount (GetAccountRequest) returns (Account);
rpc CreateAccount (CreateAccountRequest) returns (Account);
rpc ListAccounts (ListAccountsRequest) returns (ListAccountsResponse);
rpc StreamTransactions (StreamTransactionsRequest) returns (stream Transaction);
}
message GetAccountRequest {
string account_id = 1;
}
message Account {
string account_id = 1;
string type = 2;
string status = 3;
string currency = 4;
double available_balance = 5;
google.protobuf.Timestamp created_at = 6;
}
The Four gRPC Patterns
gRPC supports four interaction models that REST cannot natively provide:
1. Unary → one request, one response (like REST)
rpc GetAccount(Request) returns (Account)
2. Server streaming → one request, stream of responses
rpc StreamTransactions(Request) returns (stream Transaction)
3. Client streaming → stream of requests, one response
rpc BatchUpload(stream DataRecord) returns (UploadResult)
4. Bidirectional → stream of requests and stream of responses simultaneously
rpc Chat(stream Message) returns (stream Message)
Why gRPC Is Faster Than REST
| Factor | REST (JSON/HTTP 1.1) | gRPC (protobuf/HTTP/2) |
|---|---|---|
| Serialization | JSON text (verbose) | Protobuf binary (~3–5× smaller) |
| Multiplexing | One request per connection | Multiple concurrent streams per conn |
| Header compression | None | HPACK compression |
| Type safety | Runtime only | Compile-time from .proto |
In benchmarks, gRPC typically achieves 5–10× higher throughput and 50–70% lower latency than equivalent REST+JSON over the same infrastructure.
When to Use gRPC
✅ Service-to-service communication inside a microservices platform
✅ Low-latency, high-throughput internal APIs
✅ Streaming scenarios (server push, bidirectional streams)
✅ When strong, compile-time type safety is required
✅ Polyglot environments (Java service ↔ Go service ↔ Python service)
When gRPC Is Not the Right Choice
❌ Browser-native clients (browsers do not support gRPC directly; use gRPC-Web or a REST gateway)
❌ Public developer APIs (protobuf is less approachable than JSON for external developers)
❌ Systems where human-readable wire format is needed for debugging
❌ Environments that block HTTP/2 (some enterprise proxies)
GraphQL
GraphQL is a query language for APIs developed by Facebook (Meta) in 2012 and open-sourced in 2015. Instead of a collection of fixed endpoints, a GraphQL API exposes a single endpoint where the client sends a query describing exactly the data it needs.
How GraphQL Works
The server defines a schema — the types, relationships, and operations available:
type Query {
order(id: ID!): Order
orders(filter: OrderFilter, first: Int, after: String): OrderConnection
}
type Mutation {
createOrder(input: CreateOrderInput!): Order
cancelOrder(id: ID!): Order
}
type Subscription {
orderStatusChanged(orderId: ID!): Order
}
type Order {
id: ID!
status: OrderStatus!
customer: Customer!
items: [OrderItem!]!
totalAmount: Money!
createdAt: DateTime!
}
The client sends a query specifying exactly which fields to return:
query {
order(id: "ord_abc123") {
id
status
customer {
name
email
}
items {
productName
quantity
unitPrice
}
}
}
The response contains exactly the fields requested — nothing more:
{
"data": {
"order": {
"id": "ord_abc123",
"status": "CONFIRMED",
"customer": {
"name": "Jane Smith",
"email": "[email protected]"
},
"items": [
{ "productName": "Laptop Stand", "quantity": 1, "unitPrice": 49.99 }
]
}
}
}
Solving Over-fetching and Under-fetching
REST APIs have a fundamental problem: the endpoint defines the shape of the response, not the consumer.
Over-fetching: The response contains fields the client doesn't need.
// Client only needs name and email, but gets all of this:
{
"id": "cust_abc",
"name": "Jane",
"email": "[email protected]",
"phone": "+1-555-0100",
"addressLine1": "123 Main St",
"addressLine2": null,
"city": "Austin",
"state": "TX",
"zipCode": "78701",
"country": "US",
"dateOfBirth": "1985-04-12",
"createdAt": "2023-01-15T09:00:00Z"
}
Under-fetching: One endpoint doesn't return all the data needed, requiring multiple sequential calls.
GET /v1/orders/ord_abc123 → get order (no customer details)
GET /v1/customers/cust_abc → get customer (separate call)
GET /v1/products/prod_xyz → get product details (separate call)
GraphQL solves both with a single query that fetches exactly the required data across all related resources in one round trip.
GraphQL Operations
| Operation | Purpose | Analogy |
|---|---|---|
query |
Read data (side-effect free) | GET |
mutation |
Create, update, or delete data | POST/PUT/DELETE |
subscription |
Real-time event stream over WebSocket | WebSocket/SSE |
When to Use GraphQL
✅ Multiple client types (web, mobile, smart TV) needing different data shapes
✅ Rapidly evolving frontends that need flexibility over the backend schema
✅ Backend-for-Frontend (BFF) pattern — GraphQL as the aggregation layer
✅ Applications with complex, deeply nested relational data
✅ When reducing the number of API round trips is a priority
When GraphQL Is Not the Right Choice
❌ Simple CRUD services with uniform data needs
❌ Public APIs where HTTP caching at the CDN level is important (GraphQL POSTs are not cacheable by default)
❌ Teams without discipline around query depth limits, rate limiting per query cost, and the N+1 problem
❌ High-throughput batch processing where overhead of query parsing matters
Event-Driven APIs (Async APIs)
Event-driven APIs do not follow the request-response model. Instead of a caller asking for something and waiting for an answer, a producer publishes an event and consumers react to it asynchronously.
How Event-Driven APIs Work
flowchart LR
PRODUCER[Order Service] -->|publishes event| BROKER[(Message Broker\nKafka / RabbitMQ)]
BROKER -->|delivers event| C1[Fulfillment Service]
BROKER -->|delivers event| C2[Notification Service]
BROKER -->|delivers event| C3[Analytics Service]
The producer publishes a message to a topic or queue. Any number of consumers can subscribe independently — the producer never calls them directly and does not wait for them.
Published event (Kafka message):
{
"eventId": "evt_d4e5f6a7",
"eventType": "order.confirmed",
"schemaVersion": "1.2.0",
"aggregateId": "ord_abc123",
"producedAt": "2026-07-20T10:30:00Z",
"payload": {
"orderId": "ord_abc123",
"customerId": "cust_xyz789",
"totalAmountCents": 4999,
"currency": "USD",
"items": [
{ "sku": "LAPTOP-STAND-01", "quantity": 1, "unitPriceCents": 4999 }
]
}
}
Message Brokers
| Broker | Model | Ordering | Replay | Use Case |
|---|---|---|---|---|
| Kafka | Log / pub-sub | Per partition | ✅ Yes | High throughput, event sourcing |
| RabbitMQ | Queue / pub-sub | Per queue | ❌ No | Task queues, routing by message type |
| Amazon SQS | Queue | FIFO option | ❌ No | Managed task queue, serverless |
| Amazon SNS | Fan-out pub-sub | None | ❌ No | One-to-many notifications |
| Google Pub/Sub | Pub-sub | None | ✅ Limited | GCP event streaming |
Event-Driven Patterns
Pub/Sub (Fan-out): One producer, many consumers. All consumers receive every message.
Queue (Work queue): One producer, many competing consumers. Each message is processed by exactly one consumer (for task distribution and load balancing).
Event Sourcing: The event log is the system of record. Current state is derived by replaying events from the beginning. Every state change is captured as an immutable event.
CQRS (Command Query Responsibility Segregation): Write operations (commands) publish events. Read operations (queries) use projections built from those events.
When to Use Event-Driven APIs
✅ Multiple services need to react to the same state change
✅ The producer should not wait for consumers (temporal decoupling)
✅ High-throughput pipelines (millions of events per second)
✅ Audit logs and event sourcing
✅ Workflows that span multiple services (saga pattern)
✅ Streaming analytics and real-time data processing
When Event-Driven APIs Are Not the Right Choice
❌ When the caller needs a synchronous answer before proceeding
❌ Simple CRUD operations with low volume
❌ When message ordering is critical across multiple topics (Kafka only guarantees ordering within a partition)
❌ Teams without the operational maturity to manage message brokers, DLQs, and consumer lag
WebSocket
WebSocket is a protocol that provides a persistent, full-duplex (bidirectional) communication channel over a single TCP connection. Unlike HTTP, which is request-response, a WebSocket connection stays open — either side can send a message to the other at any time without a new request.
How WebSocket Works
A WebSocket connection starts as an HTTP request and is upgraded to a WebSocket connection:
Client → Server:
GET /chat HTTP/1.1
Host: chat.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Server → Client:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
After the handshake, the connection is persistent. Both sides can send frames at any time:
Client → Server: { "type": "message", "content": "Hello team!" }
Server → Client: { "type": "message", "from": "alice", "content": "Welcome!" }
Server → Client: { "type": "typing", "user": "bob" }
Client → Server: { "type": "ping" }
Server → Client: { "type": "pong" }
WebSocket vs HTTP Polling
Without WebSocket, clients wanting real-time updates must poll:
Client → Server: GET /notifications (every 2 seconds)
Server → Client: [] (empty — no new notifications)
Client → Server: GET /notifications (2 seconds later)
Server → Client: [] (still empty)
... (100 requests before a notification arrives)
Client → Server: GET /notifications
Server → Client: [{ "type": "order_confirmed", "orderId": "ord_abc123" }]
Every poll is a new HTTP connection. Most polls return empty. This wastes bandwidth, server resources, and battery on mobile.
With WebSocket, the server pushes the notification the instant it occurs — one persistent connection, zero wasted requests.
When to Use WebSocket
✅ Real-time chat applications
✅ Live dashboards (stock prices, monitoring metrics, sports scores)
✅ Multiplayer games
✅ Collaborative editing (Google Docs-style)
✅ Live order tracking, delivery tracking
✅ Notifications that must arrive within milliseconds
When WebSocket Is Not the Right Choice
❌ Data that updates infrequently (once a minute or less) — polling or SSE is simpler
❌ One-directional server push — Server-Sent Events (SSE) is a lighter alternative
❌ Stateless, request-response interactions — HTTP does this better
❌ Environments that terminate long-lived connections (some corporate proxies, load balancers with short idle timeouts)
Server-Sent Events (SSE) — A Simpler Alternative
For server-to-client push (one direction only), SSE is often the right choice over WebSocket:
GET /v1/notifications HTTP/1.1
Accept: text/event-stream
data: {"type":"order_confirmed","orderId":"ord_abc123"}\n\n
data: {"type":"payment_processed","paymentId":"pmt_xyz789"}\n\n
SSE uses plain HTTP, works over HTTP/2, handles reconnections automatically, and requires no WebSocket upgrade. Use it when you only need server-to-client push and bidirectional communication is not required.
Webhook
A webhook is an HTTP callback — instead of the client polling the server for updates, the server sends an HTTP POST to a URL the client has registered whenever an event occurs.
How Webhooks Work
sequenceDiagram
participant APP as Your Application
participant STRIPE as Stripe API
participant WH as Your Webhook Endpoint
APP->>STRIPE: POST /v1/charges (create a charge)
STRIPE-->>APP: { "chargeId": "ch_abc", "status": "pending" }
note over STRIPE: Payment is processed asynchronously
STRIPE->>WH: POST /webhooks/stripe
note right of STRIPE: HTTP POST with event payload
WH->>WH: Verify signature, process event
WH-->>STRIPE: 200 OK
Webhook payload delivered by Stripe:
{
"id": "evt_1NkLHb2eZvKYlo2CmSp2XXXX",
"object": "event",
"type": "payment_intent.succeeded",
"created": 1690000000,
"data": {
"object": {
"id": "pi_3NkLHb2eZvKYlo2C",
"amount": 4999,
"currency": "usd",
"status": "succeeded",
"metadata": {
"orderId": "ord_abc123"
}
}
}
}
Webhook Security: Signature Verification
Webhooks are incoming HTTP requests from the internet — anyone could POST to your webhook endpoint. Always verify the signature:
import hmac
import hashlib
def verify_stripe_webhook(payload: bytes, sig_header: str, secret: str) -> bool:
timestamp, signature = parse_stripe_signature(sig_header)
expected = hmac.new(
secret.encode(),
f"{timestamp}.{payload.decode()}".encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Webhook Reliability Patterns
Webhooks can fail — your endpoint might be down, slow, or return a non-2xx response. Production-quality webhook consumers must:
- Return 200 immediately — acknowledge receipt before doing any processing to avoid timeout retries
- Process asynchronously — put the event on an internal queue and return 200 fast
- Handle retries idempotently — Stripe retries failed deliveries; use the
event.idto deduplicate - Monitor DLQ — if all retries fail, the event goes to a dead-letter queue; alert on it
@app.route("/webhooks/stripe", methods=["POST"])
def stripe_webhook():
payload = request.get_data()
sig = request.headers.get("Stripe-Signature")
if not verify_stripe_webhook(payload, sig, STRIPE_WEBHOOK_SECRET):
return "Unauthorized", 401
event = json.loads(payload)
# Idempotency check
if event_already_processed(event["id"]):
return "OK", 200
# Enqueue for async processing
queue.enqueue(process_stripe_event, event)
return "OK", 200 # Always return 200 quickly
When to Use Webhooks
✅ Receiving notifications from third-party services (Stripe payments, GitHub pushes, Twilio SMS delivery)
✅ Integrations where the event source is external and cannot connect to your message broker
✅ B2B event delivery to partner systems
✅ Replacing polling for infrequent but important events
Webhook Limitations
❌ The consumer must have a publicly accessible HTTPS endpoint
❌ No built-in ordering guarantees — events can arrive out of order
❌ No native replay — if your endpoint was down during delivery, you may miss events
❌ Delivery guarantees vary by provider — always check the provider's retry policy
SOAP (Simple Object Access Protocol)
SOAP is an XML-based messaging protocol standardised by W3C. It defines a formal envelope structure, uses WSDL (Web Services Description Language) for machine-readable contracts, and supports a rich ecosystem of WS-* standards for security, reliability, and transactions.
How SOAP Works
Every SOAP message has the same structure:
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:tns="https://api.bank.com/accounts">
<soap:Header>
<wsse:Security xmlns:wsse="...">
<wsse:UsernameToken>
<wsse:Username>system-user</wsse:Username>
<wsse:Password Type="...PasswordText">secret</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<tns:GetAccountBalanceRequest>
<tns:AccountId>ACC-7F3A9B2C</tns:AccountId>
<tns:AsOfDate>2026-07-20</tns:AsOfDate>
</tns:GetAccountBalanceRequest>
</soap:Body>
</soap:Envelope>
SOAP response:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<tns:GetAccountBalanceResponse>
<tns:AccountId>ACC-7F3A9B2C</tns:AccountId>
<tns:Currency>USD</tns:Currency>
<tns:AvailableBalance>4250.00</tns:AvailableBalance>
<tns:AsOf>2026-07-20T09:00:00Z</tns:AsOf>
</tns:GetAccountBalanceResponse>
</soap:Body>
</soap:Envelope>
WSDL — The Machine-Readable Contract
SOAP services are described by a WSDL (Web Services Description Language) file that fully specifies every operation, input/output message, data type, and binding. WSDL allows code generation tools to produce strongly typed client stubs in any language — similar to what protobuf does for gRPC.
WS-* Standards
SOAP's extensibility through SOAP headers supports a range of enterprise standards:
| Standard | Purpose |
|---|---|
| WS-Security | Message-level encryption, signing, token-based auth |
| WS-ReliableMsg | Guaranteed delivery with acknowledgement |
| WS-AtomicTx | Distributed ACID transactions across multiple services |
| WS-Addressing | Message routing independent of transport |
These capabilities are native to SOAP and do not exist in REST. For regulated industries where end-to-end message signing, non-repudiation, and guaranteed delivery are legal requirements, SOAP remains the correct choice.
When to Use SOAP
✅ Legacy enterprise integration where SOAP endpoints are the only option
✅ Financial services integrations requiring WS-Security message signing
✅ Healthcare HL7 / FHIR integrations on older infrastructure
✅ Government system integrations
✅ ACID transactions spanning multiple systems
✅ Non-repudiation requirements (proof that a specific message was sent)
When SOAP Is Not the Right Choice
❌ New greenfield services — use REST or gRPC
❌ Mobile or browser clients — XML verbosity is costly on constrained devices
❌ Teams without SOAP tooling experience
❌ Public developer APIs where onboarding friction matters
Choosing the Right API Type
The decision is driven by the interaction model required, not by preference.
flowchart TD
A{What does the caller need?}
A -->|"Synchronous answer now"| B{Who is the caller?}
A -->|"React to something that happened"| C{Volume / frequency?}
A -->|"Push data as it becomes available"| D{Direction?}
B -->|"External developer / browser"| E[REST]
B -->|"Internal service, need speed"| F[gRPC]
B -->|"Frontend needing flexible data"| G[GraphQL]
B -->|"Enterprise / legacy system"| H[SOAP]
C -->|"High throughput / decouple services"| I[Event-Driven / Kafka]
C -->|"Third-party notification / low volume"| J[Webhook]
D -->|"Server → Client only"| K[SSE or Webhook]
D -->|"Both directions / real-time"| L[WebSocket]
Decision Guide
| Scenario | API Type |
|---|---|
| Public REST API for external developers | REST |
| Internal microservice calls in a distributed system | gRPC |
| Mobile app that needs different data than the web app | GraphQL |
| Payment confirmation from Stripe to your backend | Webhook |
| Order service notifying fulfillment + notification services | Event-Driven (Kafka) |
| Live chat application | WebSocket |
| Real-time stock price feed to a browser | WebSocket or SSE |
| Bank-to-bank SWIFT integration | SOAP |
| Order status feed (server push only) | SSE |
| Background job worker queue | Event-Driven (SQS/RabbitMQ) |
Mixing API Types in One System
Real systems use multiple API types simultaneously. A mature e-commerce platform might use:
- REST — public API for partner integrations and mobile clients
- gRPC — service-to-service calls between checkout, inventory, and pricing services
- GraphQL — backend-for-frontend aggregation layer for the web storefront
- Kafka — order confirmed → fulfillment, notification, analytics pipeline
- WebSocket — live order tracking updates pushed to the customer's browser
- Webhooks — payment processor delivers payment confirmation events
- SOAP — integration with a legacy warehouse management system
No single API type is the right answer for everything. The skill is matching the type to the use case.
Summary
flowchart LR
A[REST] -->|"Resources, HTTP, JSON"| Z[Choose based on interaction model]
B[gRPC] -->|"Procedure calls, binary, streaming"| Z
C[GraphQL] -->|"Flexible queries, client-driven"| Z
D[Event-Driven] -->|"Publish-subscribe, async"| Z
E[WebSocket] -->|"Bidirectional, real-time"| Z
F[Webhook] -->|"Server push via HTTP callback"| Z
G[SOAP] -->|"XML, enterprise, WS-* standards"| Z
Key takeaways:
- REST is the default for web and mobile APIs — stateless, HTTP-native, widely understood
- gRPC is the default for internal service-to-service calls — faster, streaming-capable, strongly typed
- GraphQL solves over-fetching and under-fetching when multiple client types need different data shapes
- Event-driven APIs decouple producers from consumers — use when asynchronous reaction and high throughput matter
- WebSocket provides real-time bidirectional communication — use for chat, live dashboards, collaborative apps
- Webhooks are server-to-client HTTP callbacks — the standard way third-party services deliver event notifications
- SOAP is the correct choice for regulated enterprise integrations requiring message-level security, signed envelopes, and guaranteed delivery
- Most production systems use several types simultaneously — the skill is choosing the right type for each integration boundary
Learning Path
This page is part of the API Engineering Fundamentals Learning Path.
Return to the API Engineering Learning Path to continue with the complete roadmap.