URL Design¶
URLs are a human-readable interface — they communicate structure, help users navigate, and set expectations.
A good URL should be guessable: if you know /companies/42/employees exists, you can intuit /companies/42/invoices without reading docs (IDs aside). Good URLs organise knowledge into a hierarchy of resources.
They also matter for machines: search engines, API clients, OpenAPI tooling, caching layers, and AI agents all benefit from predictable, consistent paths. An agent navigating an API can infer resource relationships from URL structure alone — no docs lookup needed. A well-designed URL scheme makes the API self-documenting.
Current status
These conventions are enforced on eu-tools via a runtime check.
They are the target for all backends, but there is no current plan to migrate existing routes.
Hyphens over underscores¶
Use hyphens (-) to separate words in URL segments, not underscores (_).
This follows Google's URL structure guidelines ⧉ and is the dominant convention in web APIs.
A runtime check ([validate_hyphenated_routes][shared.helpers.validate_routes.validate_hyphenated_routes]) runs on opted-in apps in development mode and raises on any violation. Path parameters (e.g. <uuid:id>) are ignored — only static segments are checked.
RESTful naming¶
Nouns, not verbs¶
URL segments should name resources (nouns). The HTTP method expresses the action.
| Method | Meaning |
|---|---|
GET |
Read |
POST |
Create |
PUT / PATCH |
Update |
DELETE |
Remove |
Plural nouns for collections¶
Use plural nouns for collection endpoints.
Sub-resources for relationships¶
Nest related resources under their parent.
Examples¶
| Good | Bad | Why |
|---|---|---|
POST /jobs/<id>/failures |
POST /jobs/enqueue-failing-job |
Noun-based, method = action |
GET /cache/keys |
GET /cache/count-keys-and-space |
Resource, not operation |
GET /alaners/<id>/coffee-profiles |
GET /alaners/<id>/get-coffee-profile |
Plural, no verb |
GET /companies/<id>/employees |
GET /get-employees-for-company |
Sub-resource hierarchy |
POST /members/<id>/claims |
POST /create-claim-for-member |
HTTP method = create |
Renaming a route safely¶
Our APIs are consumed by both the web app and the mobile app. The web app is always up-to-date on deploy, but mobile clients may run older versions for weeks. Renaming a route therefore requires a transition period.
- Add the new route alongside the old one — both should point to the same handler (or the old one redirects to the new one).
- Update the web app to call the new route.
- Update the mobile app to call the new route, and wait for adoption — the old route must stay alive until the minimum supported mobile version uses the new one.
- Remove the old route once no active client relies on it.
Warning
Never rename a route in-place in a single deploy — mobile users on older versions will get 404s.
Exceptions¶
Existing routes don't need to be renamed unless they are already being modified.
Source¶
- Runtime check:
shared/helpers/validate_routes.py⧉ - Ruler guideline:
backend/.ruler/restful_route_naming.md⧉