Versioning an API with MDM-Pinned Mobile Clients
A B2B inventory app has 12,000 monthly active users across three production versions. Telemetry shows v1 at 18%, v2 at 55%, and v3 at 27%. Enterprise customers deploy the app through MDM, and two large customers intentionally pin v1 because their warehouse scanners are validated against it. The normal release cycle is six weeks plus app review. The backend team wants to make warehouse_id required on POST /orders and replace the current error body, a single error string, with an RFC 7807 problem+json object. The API currently has no version header; clients call paths under /api/. The mobile lead proposes a hard cutover with a 30-day notice. Customer success warns that one pinned customer represents 9% of revenue and has threatened to leave if ordering breaks. The backend team argues that maintaining three behaviours is too expensive.
1. Evaluate the hard-cutover proposal against expand-contract and URI versioning. Which approach should be chosen, and why?
Read the worked answer
A hard cutover is not acceptable given MDM pinning. The organisation does not control when 18% of clients update; a 30-day notice is a hope, not a migration mechanism. Breaking POST /orders for pinned scanners would stop warehouse receiving, which is a revenue-critical workflow.
URI versioning, such as /api/v2/orders, is simple to reason about but tends to fork the entire surface. It also encourages clients to pin a path forever and forces duplicated validation, documentation, and bug fixes. It is a reasonable fallback when semantics change radically, not for adding one field and changing an error shape.
Expand-contract is the better fit. First, add warehouse_id as optional and have the server derive a default from the user's assigned warehouse when it is absent. Log every request that relied on the default. Update mobile clients to send the field explicitly. When default usage falls below the agreed threshold, make the field required.
For errors, negotiate representation rather than changing the payload for everyone: if the client sends an API-Version header or an Accept header for problem+json, return the new shape; otherwise return the old string. This lets new clients adopt the richer format while old clients keep working. The trade-off is temporary dual behaviour, but it is bounded and measurable, unlike an open-ended hard break.
2. Design the rollout and deprecation plan, including the metrics and gates that decide when v1 support can end.
Read the worked answer
Treat the rollout as three concurrent tracks: client adoption, server tolerance, and contractual communication.
The server ships backward-compatible behaviour first: optional warehouse_id with derived default, plus content-negotiated error format. Instrument requests by app version, customer tenant, and whether the default was used. Publish a deprecation timeline only after telemetry exists.
Mobile releases then send warehouse_id explicitly and parse both error shapes. Because the release cycle is six weeks, plan at least two release windows before any enforcement. For the two pinned customers, offer an MDM-deliverable patch or a server-side tenant setting, and get written confirmation of their upgrade path.
Suggested gates before making the field required: no v1 requests for 60 consecutive days; default-derived warehouse usage below 1% of orders and zero for the top ten tenants; all active versions passing contract tests; a documented fallback that returns a clear 422 rather than a 500.
Metrics to watch during rollout: 4xx rate on POST /orders by version, order creation success rate, support tickets tagged ordering, and the count of clients still receiving the legacy error shape. A deprecation date without per-version telemetry is not a plan. The chosen approach costs some temporary branching, but it converts an uncontrolled break into a measurable migration.
3. A client developer asks whether the mobile app should ignore unknown JSON fields and how it should handle the two error formats. What guidance do you give, and what are the limits?
Read the worked answer
Clients should be tolerant readers: ignore unknown fields, do not fail parsing when a new field appears, and treat missing optional fields explicitly. This is what allows the server to expand a response without shipping a new app version first.
For errors, the client should inspect the content type and body shape. If it receives problem+json, read the standard fields it understands; if it receives the legacy string, wrap it into the same internal error model. Do not branch on HTTP status alone, because validation errors may use the same status with different bodies. Keep a single error-mapping layer so UI code does not care which format arrived.
The limit is that tolerant reading cannot fix semantic breaks. If the server starts requiring a field, or changes the meaning of an existing field, the old client can still fail. That is why expand-contract must be paired with server-side telemetry and a migration gate. Also, tolerance must not become silent failure: log unknown error shapes at a low rate and alert if the share of unrecognised responses rises. The guidance is therefore: be liberal in what you accept, strict about what you send, and instrument what you do not recognise.