Every API designer eventually faces the question of how deeply to nest URL paths. The customer order item endpoint can be /customers/123/orders/456/items/789 or /items/789 or /customers/123/items/789. The choice affects URL aesthetic, authorization implementation, client SDK ergonomics, and the survivability of the API across schema evolution. The wrong default introduces costs that compound over years of integration work by customers.
The industry has gradually converged on a hybrid pattern that uses shallow nesting for primary access and flat addressing for direct lookup, with explicit reasons for each choice. The convergence is visible across Stripe, GitHub, Linear, Vercel, and other providers whose APIs see substantial third-party integration. The pattern is neither pure REST nor a complete departure from it.
What deep nesting buys and costs
Deep nesting like /customers/123/orders/456/items/789 communicates hierarchy through URL structure. A reader can see at a glance that the item belongs to an order which belongs to a customer. The authorization implementation can use the URL components directly: verify the customer matches the authenticated account, verify the order belongs to the customer, verify the item belongs to the order, then act.
The cost is that the URL becomes brittle to schema evolution. The day the business decides that items can belong to multiple orders, or that customers can share orders with other customers, or that items can exist without orders, the URL structure stops being correct. The migration is painful because customer integrations have hard-coded the structure.
Deep nesting also makes direct lookup awkward. When a webhook delivers an item ID, the consumer needs to look up the customer and order to construct the URL for fetching the item. The lookup is gratuitous when the item ID alone is sufficient to identify it.
What flat addressing buys and costs
Flat addressing like /items/789 treats every resource as independently identifiable. The URL does not encode hierarchy. The authorization is performed by examining the item's ownership rather than the URL path.
The benefit is that the URL survives schema changes. The day items move from belonging to orders to belonging directly to customers, or to multiple orders, the URL does not change. The webhook consumer can fetch the item without knowing its parents.
The cost is that the URL loses information about resource relationships. The reader cannot tell from the URL what the item belongs to. The collection endpoint for listing items needs query parameters to scope the result, which is less discoverable than a path-based parent. The authorization implementation needs to load the resource to check ownership rather than rejecting the request from URL structure alone.
The right default for B2B SaaS
The hybrid pattern that most modern APIs converge on treats each resource as having a flat canonical URL while supporting collection-scoped access via parent paths. The item example becomes /items/789 for the canonical URL plus /customers/123/orders/456/items for the listing endpoint scoped to a specific order.
The flat URLs work for direct access, webhook references, and dashboard deep-linking. The scoped collection URLs work for listing and creation operations where the parent context is meaningful.
The principle is that nesting is appropriate for collections because the parent scopes the result set. Nesting is not appropriate for individual resources because the resource has its own identity independent of any parent.
The maximum-depth question
The convention that recurs across providers is that nesting beyond two levels rarely pays back its readability and routing cost. The endpoint /customers/123/subscriptions is clear and useful. The endpoint /customers/123/subscriptions/sub_abc/items/item_def/usage_records is unwieldy.
The two-level guideline is not a hard rule but a useful default. The cases where deeper nesting earns its cost are typically tree-shaped resources like file paths or folder hierarchies where the depth is the meaning. For typical business resources, two levels covers the patterns that scoping motivates.
The plural vs singular question
The convention that has overwhelmingly won is plural for collections and the same plural with an ID for individual resources. The pattern is /customers for the collection and /customers/123 for the individual. The mixed pattern of /customer/123 for individual fights the convention and creates inconsistency that compounds across many endpoints.
The exception is singleton resources like the authenticated user's own profile or the current account's settings. The pattern /me or /account/settings is shorter and clearer than fitting singletons into a plural collection.
The verb-in-URL question
The REST default treats URLs as resource identifiers and HTTP methods as actions. The pattern POST /orders/123/cancel violates this default because cancel is a verb in the URL. The pure-REST alternative is PATCH /orders/123 with a status field change in the body.
The convergence among real APIs is that the verb-in-URL pattern is the right default for operations that do not map cleanly to CRUD. Cancelling an order, refunding a payment, publishing a draft, or sending a test event do not fit cleanly into PATCH semantics. The verb-in-URL pattern is more discoverable and harder to misuse than the alternative of overloading PATCH with magic field updates.
The right discipline is that verb endpoints take action-specific request bodies and return action-specific response shapes. They are not magic PATCH in disguise. The customer-facing contract is clearer when the action is visible in the URL.
The composability question
The URL pattern interacts with SDK generation and OpenAPI specification. The pattern /customers/{customer_id}/orders/{order_id}/items generates SDK methods that require the customer_id parameter even when the customer is not relevant to the operation. The pattern /orders/{order_id}/items generates simpler SDK methods.
The compromise that some providers adopt is exposing both, with the longer form as the canonical specification and the shorter form as an alias. The cost is two routes in the routing table and two paths in the OpenAPI spec for the same operation. The benefit is that customers get the URL shape that fits their integration without being forced into a longer form than they need.
Three patterns that fail
The first pattern that fails is encoding business logic into URL structure. The pattern /v1/eu-west-1/customers/123/orders bakes regional and version information into the URL that should live in headers or be resolved by the server. The day the regional structure changes, the URLs need to change, and customer integrations break.
The second pattern that fails is deeply nested URLs that the application does not actually enforce. The pattern /customers/123/orders/456/items/789 that lets a request through with a mismatched customer-order pair turns the nesting into security theater. Either enforce the relationship at every level or do not include it in the URL.
The third pattern that fails is URLs that change meaning across versions. The pattern /v1/customers/123 vs /v2/customers/123 returning different resource shapes for the same ID is confusing. The cleaner pattern is versioning via header or media type with URL structure stable across versions.
Our use
Our four products use shallow flat URLs for individual resources and scoped collection URLs for parent-context listing. DocuMint exposes /api/v1/invoices/{id} for individual invoices and /api/v1/customers/{id}/invoices for listing a specific customer's invoices. CronPing exposes /api/v1/monitors/{id} and /api/v1/projects/{id}/monitors. FlagBit exposes /api/v1/flags/{id} and /api/v1/projects/{id}/flags. WebhookVault exposes /api/v1/endpoints/{id} and /api/v1/projects/{id}/endpoints. The pattern is consistent across products so customers building integrations against more than one product encounter the same shape.
The verb endpoints for non-CRUD operations include POST /api/v1/monitors/{id}/pause and POST /api/v1/webhooks/{id}/replay and POST /api/v1/flags/{id}/toggle. The verb pattern is explicit about action and survives schema evolution better than overloading PATCH.
The deeper observation is that URL design is one of the API surfaces that customers see most frequently. The choices made at launch propagate through integration code, SDK methods, customer documentation, and support tickets for as long as the API exists. The cost of a thoughtful URL pattern at launch is small. The cost of an inconsistent or unmaintainable URL pattern is paid for the life of the API.
Our products: DocuMint (PDF invoice generation API), CronPing (cron job monitoring with status pages), FlagBit (feature flags API for modern teams), and WebhookVault (webhook capture and replay) put these patterns into production.