Skip to Content
GuidesApp Design GuideURL Routing

URL Routing

Every app has a single urls.py with a DefaultRouter. ViewSet actions generate URL patterns automatically.

Basic Router Setup

# urls.py from rest_framework.routers import DefaultRouter from .views.api.agent_viewsets import AgentViewSet from .views.api.memory_viewsets import MemoryViewSet from .views.api.summary_viewsets import TradingSummaryViewSet router = DefaultRouter() router.register("agents", AgentViewSet, basename="agents") router.register("memory", MemoryViewSet, basename="memory") router.register("summaries", TradingSummaryViewSet, basename="summaries") urlpatterns = router.urls

From agents (stockapis-trader) — three ViewSets in one router.

This generates:

/agents/ → list, create /agents/{pk}/ → retrieve, update, destroy /agents/{agent_id}/status/ → @action(detail=True) /memory/ → list, create /memory/search/ → @action(detail=False) /summaries/ → list

App URL Registration

Apps are registered in the central URL config:

# django_cfg/apps/urls.py urlpatterns = [ path('cfg/agents/', include('django_cfg.apps.trading.agents.urls')), path('cfg/market/', include('django_cfg.apps.trading.market.urls')), ]

Multiple Resources in One Router

When an app has several related resources, register them all in one router:

# urls.py router = DefaultRouter() router.register("decisions", TradeDecisionViewSet, basename="decisions") router.register("tool-executions", ToolExecutionViewSet, basename="tool-executions") router.register("ledger", NumericalLedgerViewSet, basename="ledger") router.register("transitions", FSMTransitionViewSet, basename="transitions") urlpatterns = router.urls

From trading (stockapis-trader) — 4 resources under one app.

Path-Parameter Actions

For hierarchical URL patterns within a single ViewSet:

@action( detail=False, methods=["get"], url_path="(?P<exchange>[^/]+)/(?P<market_type>[^/]+)/symbols", pagination_class=None, ) def by_exchange_market(self, request, exchange, market_type): symbols = Symbol.objects.filter( exchange=exchange, market_type=market_type, status="active", ) return Response(SymbolListSerializer(symbols, many=True).data)

From symbols (stockapis) — generates /symbols/binance/linear/symbols.

Another example from the same ViewSet:

@action( detail=False, methods=["get"], url_path="(?P<exchange>[^/]+)/(?P<market_type>[^/]+)/quote-assets", ) def quote_assets(self, request, exchange, market_type): assets = Symbol.objects.filter( exchange=exchange, market_type=market_type ).values("quote_asset").annotate(count=Count("id")) return Response(QuoteAssetsResponseSerializer({"assets": assets}).data)

Generates /symbols/bybit/spot/quote-assets.

Custom Lookup Fields

When the URL should use a human-readable slug instead of the numeric PK:

class AgentViewSet(viewsets.ModelViewSet): lookup_field = "agent_id" # Instead of "pk" queryset = Agent.objects.all()

Generates /agents/{agent_id}/ instead of /agents/{pk}/.

class SkillViewSet(viewsets.ModelViewSet): lookup_field = "slug" queryset = Skill.objects.all()

Generates /skills/{slug}/ instead of /skills/{pk}/.

Nested Routers

For parent-child relationships use drf-nested-routers:

from rest_framework_nested.routers import NestedDefaultRouter router = DefaultRouter() router.register("workspaces", WorkspaceViewSet, basename="workspaces") # Nested: /workspaces/{workspace_pk}/members/ workspaces_router = NestedDefaultRouter(router, "workspaces", lookup="workspace") workspaces_router.register("members", WorkspaceMemberViewSet, basename="workspace-members") urlpatterns = router.urls + workspaces_router.urls

URL Naming Conventions

PatternExample
Resource namerouter.register("symbols", ...) — plural, kebab-case
Basenamebasename="symbols" — matches resource name
Action url_pathurl_path="sync" — lowercase verb or noun
Nested path params(?P<exchange>[^/]+) — snake_case param name

Single Router Per App

Each app should have exactly one urls.py with one DefaultRouter:

# ✅ Correct — one router router = DefaultRouter() router.register("keys", APIKeyViewSet, basename="api-key") urlpatterns = router.urls # ❌ Wrong — multiple routers in one app router1 = DefaultRouter() router2 = DefaultRouter()

If your app has resources that belong to different URL prefixes, consider splitting into separate apps.

Last updated on