ddtcorex

04 Oct 2026 · 6 min read

The four API surfaces of Magento 2 and when each is wrong

REST, GraphQL, bulk async endpoints, and message queues compared by failure mode, with the concrete rule for picking the right Magento 2 surface

Part 1 of the API Platform series — pick the wrong surface and every later optimization is damage control.

Magento 2 exposes four distinct API surfaces, and most integration pain comes from using a good surface for the wrong job. REST is the default. GraphQL is the storefront favorite. Bulk and async endpoints handle volume. Message queues handle time. Each one has a workload where it shines and a workload where it silently punishes you. This post maps all four, names the failure mode of each, and gives you the decision rule we use before writing a single line of integration code.

If you already build storefront queries, the companion post Magento 2 GraphQL: building a real storefront query covers the products, cart, and customer queries you will actually write. This series goes one level deeper: how the platform pieces fit together, starting with which surface to stand on.

1. REST: the contract surface

REST in Magento 2 is service-contract driven. Every POST /rest/V1/orders call resolves to a service contract interface (Magento\Sales\Api\OrderRepositoryInterface and friends), with webapi.xml declaring which routes map to which interface methods. The payloads are typed, the ACL rules attach per resource (Magento_Sales::actions_view and similar), and third-party systems understand the shape without Magento-specific tooling.

Use REST when the consumer is another system, not a screen. ERP sync, OMS callbacks, PIM imports of single records, payment gateway webhooks: all of these want stable contracts, per-resource permissions, and predictable versioning. A REST endpoint is also the easiest surface to debug in production, because each request maps to one operation with one status code and one log line.

REST is wrong in two situations. First, when the client is a storefront screen that needs product data, price data, stock data, and review data at once. That becomes four to six round trips, each with its own authentication overhead and serialization cost, and latency adds up linearly. Second, when the workload is volume: importing 50,000 SKUs one REST call at a time means 50,000 HTTP handshakes, 50,000 ACL checks, and 50,000 transaction commits. It works, and it takes all night.

A practical rule: if one user action needs more than three REST calls to render, you are on the wrong surface. If one job issues more than a few hundred REST calls in a loop, you are on the wrong surface.

2. GraphQL: the screen surface

GraphQL exists to solve the round-trip problem. One POST /graphql request fetches exactly the fields the screen renders, across catalog, pricing, inventory, and CMS domains, in a single response. The schema is strongly typed, introspectable, and versioned by extension rather than by URL version. For headless storefronts, mobile apps, and PWA front ends, nothing else comes close on network efficiency.

The cost is backend complexity, which Parts 2 through 4 of this series cover in detail. Every GraphQL field needs a resolver, resolvers that load entities one at a time create the N+1 problem, and caching a POST /graphql response requires explicit query hashing and cache-tag discipline because the default full-page cache path was built for GET routes. None of this is a reason to avoid GraphQL. It is a reason to budget for schema design, resolver batching, and caching work instead of assuming GraphQL is faster by default.

GraphQL is wrong when the consumer is a backend system doing bulk writes. Mutations are single-entity oriented (createProduct, updateCartItems), there is no bulk mutation surface, and driving a 100,000-record migration through GraphQL mutations replays the one-at-a-time problem REST has, with larger payloads. It is also wrong for operations that take minutes: a GraphQL request holds an HTTP connection open, and PHP worker timeouts will kill a long-running mutation long before it finishes.

3. Bulk and async REST: the volume surface

The bulk endpoints (POST /rest/async/bulk/V1/products, /rest/async/bulk/V1/customers, and the bulk order endpoints) accept arrays of operations, persist them, and return a bulk UUID the client polls for completion. Under the hood they publish to the message queue, so the HTTP request returns fast while consumers process the operations in the background. The async variants (/rest/async/V1/...) do the same for single operations that should not block the caller.

Use bulk endpoints when the operation count is high and each operation is independent. Catalog imports, price updates across thousands of SKUs, customer migrations, and inventory feeds are the textbook cases. Throughput rises by an order of magnitude over sequential REST because the queue consumers batch database work and the HTTP layer stops being the bottleneck.

Bulk is wrong when operations must succeed or fail together. There is no distributed transaction across a bulk request: each operation completes or fails on its own, and the status endpoint reports per-operation results the caller must reconcile. If your import requires all-or-nothing semantics, bulk endpoints will corrupt that assumption quietly, succeeding on 9,997 records and failing on three. Bulk is also wrong for latency-sensitive flows. "Async" means the caller does not know when the work finishes, so checkout, payment capture, and anything the shopper waits on must stay synchronous.

4. Message queues: the time surface

Message queues (queue.xml, publishers, consumers, queue_consumer cron or daemon processes) decouple work from time. The publisher declares "this happened" and returns; a consumer processes it seconds or minutes later, with retries on failure. Magento ships queue-backed flows for bulk operations, inventory reservations, and asynchronous order processing, and custom modules can declare their own topics for anything deferrable: indexation triggers, webhook fan-out, export generation, email dispatch.

Use queues when the work can be late but must not be lost. Retries, dead-letter handling, and horizontal consumer scaling are built in, which makes queues the only surface that survives a downstream outage gracefully. If the ERP is down for an hour, queued export messages wait; synchronous REST calls fail and require manual replay.

Queues are wrong when the caller needs the answer now. Splitting "place order" into a queued flow means the shopper sees "processing" instead of a confirmation number, and every downstream system (fraud check, payment capture, inventory hold) must handle the pending state. Queues are also wrong as a first resort for small teams: they add operational surface (consumer processes to monitor, stuck messages to inspect, schema drift between publisher and consumer payloads) that only pays off above a real volume or reliability threshold.

The decision rule

Ask three questions in order:

  1. Does a screen wait on this? Yes: GraphQL for reads across domains, synchronous REST for single-entity writes. No: continue.
  2. Is the volume high (hundreds or more operations per run)? Yes: bulk endpoints for independent operations, queues for deferrable work. No: continue.
  3. Must it be transactional? Yes: synchronous REST with explicit error handling per call. No: whichever surface above fits, preferring the simplest one.

The most expensive mistake is using synchronous REST for bulk imports, because it works just well enough to ship and just badly enough to own your nights. The second most expensive is using GraphQL mutations for backend batch jobs, because the schema makes it feel productive until the timeouts start. The third is putting checkout-adjacent writes on queues to "improve performance" and discovering that eventual consistency is a shopper-facing state, not an implementation detail.

What this series covers next

With the surface chosen, the real engineering begins. Part 2 designs a GraphQL schema extension that survives upgrades: schema.graphqls structure, type extension versus type replacement, and why preference overrides on core resolvers break on minor versions. Parts 3 and 4 then fix the two problems every production GraphQL deployment hits: resolver N+1 and response caching.

Next in this series:

Continue with magento2-graphql-schema-design.

magento2apiarchitecture