01The nesting rule of thumb
Nest when the child cannot meaningfully exist without the parent: order items belong to an order, comments belong to a post. One level — /orders/{orderId}/items — is the sweet spot: the relationship is visible, the URLs stay short, and clients can guess the structure. Reach for nesting when the parent identifier is required to scope the child, not merely as a filter.
02When to flatten
Flatten when the child has independent identity or multiple parents. A product belongs to a category, but it also has its own page, its own reviews and its own inventory — so give it a top-level /products/{productId} and use ?category= for the filtered view. Flattening also wins when clients need the child without knowing the parent: forcing them to fetch the parent first just to build a URL is hostile API design. As a rule, every nested resource deserves a top-level alias if it can stand alone.
03Two levels is a smell, three is a bug
/customers/123/orders/456/items is already hard to document, hard to cache and hard to keep stable — every relationship change ripples through client code. Three levels (/a/1/b/2/c/3/d) is a sign the data model is leaking into the URL. If you catch yourself nesting that deep, stop and flatten: expose the deepest resource top-level with query filters. Your future self, debugging at 2am, will thank you.
04Versioning: URL wins for public APIs
Three schools exist: URL versioning (/v1/users), header versioning (Accept: application/vnd.api.v2+json), and query versioning (?version=2). For public and partner APIs, URL versioning wins on visibility: the version is in every log line, every bug report and every Stack Overflow question. Header versioning is cleaner in theory and invisible in practice — which is exactly why debugging it hurts. Pick one scheme and never mix them in the same API.
05What versioning promises
A version number is a contract: within v1, existing fields keep their meaning and existing clients keep working. Additive changes (new optional fields, new endpoints) are fine; renames, removals and semantic changes require v2. Start at v1 explicitly from day one — "unversioned for now" becomes permanent, and then your first breaking change has nowhere to go. Deprecate old versions loudly (Sunset headers, changelogs, emails) and give clients a real migration window.
06The /me pattern
For the current user, /me (or /api/v1/me/orders) beats /users/{userId} with a magic ID. It is shorter, it cannot leak other users' IDs through enumeration, and it reads naturally in client code. Treat /me as an alias resolved from the auth token — never as a real user record with ID "me".
Nest for ownership, flatten for independence, version in the URL, and never make a client fetch three parents to build one URL.