Chapter 01 of 10
Resources and URLs
How to pick the things your API exposes, why nesting stops paying after one level, what a path convention is worth, and which identifiers are safe to put in a URL.
The resource model is the first decision and the most expensive one to revisit, because every URL you publish inherits it. Everything else in this guide sits on top of the nouns you pick here.
Model the domain, not the procedure
The common failure is exposing an implementation as an endpoint. Something like POST /runSyncJob?mode=full describes how your system works internally, and it breaks the moment the internals change.
The alternative is to name the thing rather than the action, and let the method carry the verb. A sync becomes a resource that can be created, read and cancelled, which means a caller who knows how to create one already knows how to read it. The API design principles post covers that reframing with worked examples.
What the short version leaves out is where the rule stops. Some operations genuinely have no noun, and forcing one produces worse names than the verb you were avoiding. Those cases get their own chapter on actions that are not CRUD, because the answer there is a trade-off rather than a principle.
Collections and items
Almost every resource shows up twice, once as a collection and once as an item inside it, and the two behave differently enough to be worth separating.
A collection accepts a create and answers a list, so it needs pagination, filtering and a sort order from the start. An item accepts a read, a replace, a partial update and a delete, and it needs an identifier that stays valid. Callers assume that the item under a collection has the same shape as the entries the collection returns, and breaking that assumption is a surprise you will explain repeatedly.
The one place teams disagree is whether the collection response wraps its items in an envelope. An envelope costs one level of indirection and buys somewhere to put paging information, which is why most APIs that page at all end up with one.
Nesting expresses ownership, once
Nesting reads well at one level, because the path then states a fact about ownership that a caller can rely on.
The failure is gradual rather than sudden. Each additional level looks reasonable on its own. The cost only shows up when a caller holds a leaf identifier and has to reconstruct the ancestors to build a URL. At that point clients start caching identifiers they should never have known, and moving a record to a different parent becomes a breaking change.
A useful test is whether the leaf can be found without its ancestors. If a payment can be looked up by its own identifier alone, it deserves a top-level collection and a reference to the invoice it belongs to. If it genuinely cannot exist outside its parent, one level of nesting is telling the truth.
Naming the segments
Three naming decisions repeat in every API. All three are conventions rather than requirements.
The first is plural against singular for collections, where plural is the more common choice. The second is the case of multi-word segments, where kebab-case is common and lowercase avoids case-sensitivity bugs between servers. The third is whether the same concept keeps the same word everywhere, which is the only one of the three with an obviously wrong answer.
Because none of these is a standard, an existing API has no correctness argument for migrating. It has a consistency argument. That argument is usually weaker than the cost of breaking every published URL. The realistic move on an existing API is to fix the convention going forward and enforce it on new paths.
Identifiers are part of the contract
The moment an identifier appears in a URL, it stops being an implementation detail. A caller will store it, log it, and put it in a support ticket eighteen months from now.
That has two consequences worth deciding up front. A sequential key exposes your record count and makes neighbouring records guessable, so most APIs expose an opaque identifier instead. And whichever you expose, you can no longer change the underlying key without breaking clients, which is a constraint on the database rather than on the API.
Some resources also have a natural key, such as an order number that already exists in the business. Accepting both that key and an internal identifier at the same path is convenient and ambiguous, so it is worth choosing one and offering the other as a filter on the collection.
In Routebase
Endpoints live in a folder tree rather than a flat list of tag groups, and that tree is the same structure readers see in the published reference documentation.
Folders and tags do different jobs on purpose. Folders define the visible hierarchy, while tags are flat labels that cut across it, and a tag carries a description that is exported as the OpenAPI tag description. Because the two can drift apart, renaming a folder asks what should happen to the tags, and it tells you which choice changes the exported contract and which does not.
Path variables are read out of the path itself. Creating GET /users/{id} also creates the path parameter, and removing it from the path removes the parameter, with a dialog first when the parameter carries a description or an example you would lose.
The path convention is a style guide rule rather than a fixed opinion. The rule for URL paths defaults to kebab-case and also accepts camelCase, PascalCase and lowercase, chosen from a dropdown in the rule table. So a team that has always written /userProfiles can keep the check without changing its URL scheme. Uniqueness is checked at a level that matches how OpenAPI reads paths, since /items/{id} and /items/{itemId} address the same route and are reported as a collision. The Endpoints guide covers the tree and the tag behaviour, and the Style Guide covers the naming rules and their severities.
Frequently asked questions
Should REST resources be plural or singular?
Plural for collections is the common convention, so a list is at the collection path and one item sits under it with an identifier. Neither form is required by any specification, and both are readable. What matters is that you use the same form for every collection in the API, because a caller who has learned one path is trying to guess the next one.
How deep should a REST URL be nested?
One level of nesting expresses ownership and stays readable. Beyond that the path starts encoding a traversal rather than an address, and callers end up holding identifiers they should never have needed. When a nested resource has an identity of its own, give it a top-level collection and let it reference its parent by identifier instead.
Should API URLs use kebab-case or camelCase?
Kebab-case is the more common choice for path segments, and lowercase paths avoid an entire class of case-sensitivity bugs. Neither is a standard, so an existing API with camelCase paths has no correctness reason to migrate. The decision worth enforcing is that one convention applies to every path in the API.
Should database IDs be exposed in API URLs?
A sequential database key in a URL tells a caller how many records you have and makes neighbouring records guessable, so most teams expose an opaque identifier instead. The identifier also becomes part of the contract the moment it appears in a URL, which means the internal key can no longer be changed freely. Both of those are reasons to decide the identifier deliberately rather than inheriting it.
Last reviewed by The Routebase Team.