API styles
REST, RPC, GraphQL, gRPC and streaming: what each is for.
Create a free account Try part of it here
Why it matters
"API" covers several quite different designs, in the way that "restaurant" covers both a buffet and a set menu. The Claude Messages API is REST-style JSON over HTTP, and it streams with server-sent events. MCP uses JSON-RPC. Payment providers send webhooks. Big companies wire their internal services together with gRPC. Knowing the styles lets you read any API's documentation quickly, and pick the right one when you design your own.
What you'll be able to do
- Explain REST's idea of resources and methods, and map actions onto it
- Describe RPC styles, JSON-RPC and gRPC, and where each is used
- Explain what GraphQL solves, and build a small field selector
- Choose between polling, webhooks, server-sent events and WebSockets for live updates
What it covers
- REST: nouns and verbs
- RPC: call a function over the network
- GraphQL: ask for exactly the fields you want
- Over-fetching, measured
- The same request in three styles
- Live updates: four ways to hear about changes
- Picking a style, and what it costs
- Choose one
REST: nouns and verbs
Think of a library. Every book has its own place, and there are only a few things you can do with one: look it up, add a new one, replace it or throw it out. Once you know how that works, you can use any library.
REST (representational state transfer) organizes an API the same way. The things in your system, such as campaigns, orders and customers, are its resources: the nouns. Each resource has an address. The standard HTTP methods are the verbs:
| action | method and path | success status |
|---|---|---|
| list campaigns | GET /campaigns | 200 |
| read one | GET /campaigns/cmp_103 | 200 |
| create one | POST /campaigns | 201 |
| replace one | PUT /campaigns/cmp_103 | 200 |
| change some fields | PATCH /campaigns/cmp_103 | 200 |
| delete one | DELETE /campaigns/cmp_103 | 204 (no body) |
A few more ideas come with it:
- Each request stands alone. It carries everything the server needs, so the server keeps no memory of past requests. That's called being stateless. Picture a shop where the staff never remember you, so you bring your receipt on every visit.
- Responses say whether they can be cached, meaning whether a saved copy may be reused.
GETnever changes anything. It only reads.
Because every REST API uses the same verbs, a developer who knows one can guess most of another.
routes = {("GET", "/items"): "list", ("POST", "/items"): "create"}
print(routes[("POST", "/items")])Show the answer
create
The key ("POST", "/items") looks up create. In REST, the same path means different things depending on the method.
RPC: call a function over the network
Not every job is about a noun. "Pause this campaign" or "send this invoice" are actions, and it can feel forced to describe them as a change to a record. RPC (remote procedure call) names the action directly: pause_campaign, send_invoice, tools/call. To you it looks like calling a function, except that the function runs on another computer.
There are two common kinds:
- JSON-RPC sends
{"jsonrpc": "2.0", "method": ..., "params": ..., "id": ...}, usually all to one URL. MCP is built on it. - gRPC, from Google, describes each function and its message types in a
.protofile. Tools generate client and server code from that file in many languages. Messages travel as compact binary Protocol Buffers over HTTP/2, and calls can stream in both directions. It's popular between a company's internal services, where speed and strict types matter more than traffic a person can read.
Show the answer
DELETE /orders/ord_88, or a PATCH setting its status to "canceled"
GET must never change anything, because browsers, crawlers and caches may repeat it freely. Use DELETE, or PATCH the order's status if canceled orders are kept.
GraphQL: ask for exactly the fields you want
A REST endpoint is a set menu: you get what's on the plate. That causes two problems. A phone screen showing only campaign names still receives every field, which is over-fetching. And a page needing a customer and their orders has to make two or three calls, which is under-fetching.
GraphQL, from Facebook, is ordering exactly what you want. There's one endpoint. The client sends a query describing the shape it wants, and gets back exactly that:
query {
customer(id: "c_42") {
name
orders(last: 3) { id total }
}
}
The answer is JSON with the same nesting: a name, and three orders with two fields each.
In plain words: with REST, the server decides what comes back. With GraphQL, the client does.
The cost moves to the kitchen. One query can ask for a huge amount of data, so servers must limit how deep and how expensive a query can be. Caching is also harder, because every request is a POST to the same URL.
Over-fetching, measured
A price list on a phone needs two fields per product. REST hands back the whole record, unless you've built a way to ask for less. GraphQL lets the client name the fields it wants.
import json products = [{"id": n, "name": f"Tent {n}", "price": 89.0, "stock": 14, "description": "A roomy two-person tent. " * 10, "reviews": list(range(20))} for n in range(20)] rest = json.dumps(products) graphql = json.dumps([{"name": p["name"], "price": p["price"]} for p in products]) print("REST bytes:", len(rest)) print("GraphQL bytes:", len(graphql)) print("REST is", round(len(rest) / len(graphql)), "times bigger")
Both answers hold what the page needs, but one is far larger. On a slow connection, that's real waiting. GraphQL pays for this elsewhere: caching gets harder, and any client can write an expensive query.
Show the answer
The client asks for exactly the fields it needs
GraphQL queries name their fields, which avoids over-fetching. The trade-off is more work on the server and harder caching.
The same request in three styles
Fetching one campaign's name and spend, shown as the request each style sends. It's the same question, packaged three ways.
import json rest = ("GET", "/v1/campaigns/cmp_103", None) json_rpc = ("POST", "/rpc", { "jsonrpc": "2.0", "id": 1, "method": "get_campaign", "params": {"id": "cmp_103"}, }) graphql = ("POST", "/graphql", { "query": 'query { campaign(id: "cmp_103") { name spend } }', }) for name, (method, path, body) in [("REST", rest), ("JSON-RPC", json_rpc), ("GraphQL", graphql)]: print(f"{name:9} {method:4} {path:22} {json.dumps(body) if body else ''}")
REST puts the "what" in the URL and the method. JSON-RPC puts it in the body as a function name. GraphQL puts a whole query in the body, including which fields to return. All three travel over the same HTTP you met last lesson.
Live updates: four ways to hear about changes
Say you're waiting for a parcel. You could phone the company every hour. They could text you when it ships. You could watch their live tracking page. Or you could stay on a call with the driver. Those are the four ways an app can hear about changes:
| technique | how it works | good for |
|---|---|---|
| polling | the client asks "anything new?" every few seconds | simple dashboards; wasteful at scale |
| webhooks | the provider POSTs to your server when something happens | payments, form submissions, CI results |
| server-sent events (SSE) | one long HTTP response that keeps sending events | one-way streams to a client: LLM tokens, progress |
| WebSockets | an upgraded connection that both sides can send on at any time | chat, multiplayer, live collaboration |
- Polling is phoning every hour. It's simple, and most calls are wasted.
- Webhooks are the text message. The provider contacts you, at a web address you gave it.
- Server-sent events are the tracking page: one long reply that keeps adding news, in one direction only.
- WebSockets are the phone call. Either side can speak at any time.
The Claude API streams tokens with SSE. Each event is a few lines like event: content_block_delta then data: {...}, separated by blank lines. That's why streaming works through ordinary HTTP libraries and proxies: to them, it's a normal response that takes a while to finish.
Pick the simplest one that does the job.
Show the answer
A webhook: the provider POSTs an event to your URL
Webhooks push server-to-server events as they happen. A later lesson covers verifying and retrying them.
Picking a style, and what it costs
GET must not change anything. Browsers, caches and crawlers all feel free to repeat a GET, and to send one on their own. A "delete" link built on GET can be triggered by a link-preview bot. Anything that changes data needs POST, PUT, PATCH or DELETE.
REST means more than JSON over HTTP. An API with one URL, /api, that takes {"action": "delete_user"} is RPC, whatever its docs call it. That's not automatically wrong, and RPC is a good fit for actions that don't map onto resources. But knowing which one you have sets the right expectations, particularly for caching and retries.
GraphQL moves the cost, rather than removing it. Clients get exactly the fields they want. In exchange, your server has to protect itself from expensive queries, and URL-based caching stops working, since everything is a POST to one address. It pays off when many different clients need different shapes of data. For one web app talking to its own backend, plain REST is often simpler.
Polling is wasteful when the provider can tell you. Asking every second means 86,400 requests a day, and nearly all of them say "nothing new". If webhooks are available, use them. Keep a slow poll as a backup for missed events, since webhooks can fail too.
Choose one
Pick one style for your API, and use it throughout. People learn an API by guessing from the last endpoint they used, and every exception breaks that guess.
Practice what you just read
- 2 code examples you can edit and run
- a practice exercise that checks your code
- a 5-question quiz
- 5 review cards for spaced repetition
Try a quiz question
Which method should never change data on the server?