API · Rest

REST API Partial Responses

Field selection and sparse fieldsets for REST APIs -- letting clients ask for only the fields they need, and why this matters more for mobile clients and high-volume endpoints than for internal APIs.

John Kihiu12 min read

A REST endpoint that always returns every field on a resource is simple to build and wasteful to consume: a mobile client rendering a list view that shows only a name and a thumbnail still pays the full bandwidth and parse cost of every field the detail view would need. Partial responses — letting the client specify which fields it wants back — trade a small amount of server-side complexity for a real reduction in payload size, and they matter more the further the client is from the server, both in network distance and in how little of the full resource it actually needs.

Sparse fieldsets via a query parameter

The common convention, following JSON:API's lead, is a fields query parameter listing the fields to include: GET /invoices?fields=id,status,total_cents returns only those three fields per invoice instead of the full resource. This is simplest to implement when your data layer can project only the requested columns at the database level — fetching the full row and then stripping fields in application code still pays the database and serialization cost you were trying to avoid, so the win only materializes if field selection reaches all the way down to the query.

HTTP · SPARSE FIELDSET
GET /v1/invoices?fields=id,status,total_cents HTTP/1.1

HTTP/1.1 200 OK
{
  "data": [
    {"id": 4471, "status": "open", "total_cents": 152000},
    {"id": 4472, "status": "paid", "total_cents": 89000}
  ]
}

GraphQL solved this problem more thoroughly, at a different cost

It's worth naming honestly: GraphQL's entire value proposition is client-specified field selection done properly, including across nested relations, with a schema that makes exactly this negotiable per-request. If partial responses are the main problem you're solving, GraphQL solves it more completely than bolting sparse fieldsets onto REST. The trade-off is the rest of what GraphQL brings along — a single endpoint instead of resource URLs, harder HTTP caching, a query complexity/cost analysis problem, and a steeper operational learning curve. Sparse fieldsets on REST are the right call when you want 80% of the bandwidth win without adopting a second API paradigm.

Expanding related resources on demand

The inverse problem — a client that needs more than the base resource, including a related object inline rather than as a separate round trip — is commonly solved with an expand or include parameter: GET /invoices/4471?expand=customer embeds the customer object inside the invoice response instead of forcing a second request. This avoids both the N+1-request problem of a client fetching each related resource separately and the always-return-everything problem of eagerly embedding relations nobody asked for.

Cap and validate the fields parameter server-side

An unvalidated fields parameter that's passed straight into a SQL column list is a SQL injection vector if not parameterized correctly, and an unbounded expand parameter can be abused to force expensive joins across many relations in one request. Validate both against an explicit allowlist of real field and relation names.

Caching gets more complicated once responses vary by requested fields

Once the response shape depends on a query parameter, a shared cache needs to key on that parameter too, or it will serve one client's requested subset to another client that asked for different fields. This is manageable — include the normalized fields value in the cache key, same as you'd handle any other query-parameter-driven variation — but it's easy to forget if partial responses were bolted on after the caching layer was already built assuming one canonical shape per URL.

Don't let partial responses change your error or pagination fields

Metadata fields like pagination cursors, error objects, and _links should never be filterable by a fields parameter meant for the resource's own data — conflating the two makes the parameter's scope unpredictable and risks a client accidentally requesting a response with no way to page further.

Wrapping up

Sparse fieldsets and resource expansion get REST most of the bandwidth benefit GraphQL offers, for a fraction of the architectural cost, as long as field selection is enforced with an allowlist and actually reaches the database query rather than filtering an already-fetched full row. If per-request field selection across deeply nested relations is the core problem, that's the signal to consider GraphQL instead of pushing REST further than this pattern comfortably goes.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.