Endpoints: What We Learned, What's Improving, and What's Next
Enhanced endpoints for SDv1 gives every service automatic endpoint visibility without manual key request configuration. For services where the web framework provides http.route, this works well - clean endpoint names like GET /api/orders with no setup.
But for services where the framework doesn't provide http.route - Nginx, Kong, Apache, IIS, WebSphere Liberty, and others - endpoints fall to generic names like GET /* or POST /*. This affects traces and service calls too, not just the endpoint list. For a large portion of enterprise HTTP workloads, the result was reduced visibility rather than improved visibility.
What's changing: automatic URL normalization
We're shipping a change that derives endpoint names directly from the URL path when http.route isn't available. Your URL paths already contain the route information - they just have volatile segments (IDs, UUIDs, tokens) mixed in. We apply heuristic rules to identify those volatile segments and truncate the path at the first one, producing a stable, low-cardinality endpoint name.
Examples:
|
URL path |
Endpoint name |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
The rules identify volatile segments by recognizing patterns common in IDs and tokens: segments with multiple digits, hex values, base64-style mixed-case strings, and all-uppercase tokens. When a volatile segment is found, the path is truncated before it. Paths with no volatile segments pass through untouched. Validated against hundreds of thousands of real URL paths across multiple enterprise environments, the algorithm delivers meaningful endpoint names for up to 75% of real-world paths that lack http.route.
The derived route is written to http.route on the span, so it flows through your existing endpoint detection rules, metrics, and traces consistently. A marker on the span distinguishes framework-provided routes from derived ones, so you always know which is which.
How normalization works with your existing rules
Normalization and manual rules are not alternatives - they layer. Here's how endpoint names are resolved, in priority order:
|
Priority |
Source |
What it does |
Config needed |
|---|---|---|---|
|
1 |
Framework |
Best endpoint names - provided by your web framework |
None |
|
2 |
Your manual rules |
Exact endpoint names where you want them |
Per-service |
|
3 |
Normalization heuristics |
Good-enough defaults for everything else |
None |
|
4 |
Fallback |
What you're trying to avoid |
None |
Your existing rules take precedence. If you've already created request naming rules (SDv1) or URL path pattern matching rules (SDv2), those rules continue to produce the exact endpoint names you defined. Normalization only applies to requests that don't already have a name from a higher-priority source.
This means you don't have to choose. Normalization handles the bulk automatically - the heuristic produces stable, low-cardinality endpoint names for the majority of paths without http.route. For the remaining endpoints where you want exact control over the grouping, you write rules for those specific services. Instead of writing hundreds of rules to cover every service, you write a handful for the ones where precision matters.
The two approaches coexist cleanly. Normalization runs on every span, so a derived http.route is always available downstream. When a URL path pattern matching rule matches the request, the rule's output wins as the endpoint name; the normalized value is still on the span for trace and span-query workflows. When no rule matches, normalization is what shows up as the endpoint. Either way, write patterns for your most important services and let the heuristic handle the long tail - the two never conflict over the endpoint name.
Why some endpoints show as METHOD /*
Enhanced endpoints uses http.route when it's available. When it's missing, the endpoint falls back to {method} /*.
The confusing part is that this can happen inconsistently within a single service - some requests have http.route and produce clean endpoints, while others on the same service don't and fall to /*. This reflects inconsistent instrumentation coverage, which varies by framework, request path, and configuration. It isn't a bug.
Most modern application frameworks - Spring Boot, Express.js, link Core, Django, Flask, FastAPI - provide http.route automatically. Web servers and reverse proxies generally don't. If your service sits behind Nginx, Apache, IIS, or a similar gateway, the gateway's spans typically lack http.route even though the backend framework's spans have it.
The URL data is always there. Even when an endpoint shows as GET /*, the full URL path is captured on every span. You can see it through the View URLs button on any endpoint, or query it directly in notebooks and dashboards. Normalization improves the default grouping, but you always have access to the underlying URLs.
How it rolls out
SDv1: Normalization applies when enhanced endpoints is enabled. If you already have enhanced endpoints on, we'll reach out individually before enabling the improvement, since it changes your endpoint names. New tenants get it automatically and there is no separate toggle for normalization on its own - if you need a different grouping, write a naming rule for the affected service.
SDv2: Normalization is built into endpoint detection. New tenants get it on by default. For existing tenants, we are auto-applying it as opt-out to the top 100 tenants by span volume - those customers receive the improvement automatically and can disable it by writing a naming rule. Other existing tenants can opt in via the endpoint detection settings; we kept this gated so we don't add a rule to every tenant's ruleset by default.
If you need exact endpoint names today
If you can't wait for normalization and need specific endpoint names now, you can create rules manually. These rules will continue to work alongside normalization when it ships - they take precedence, so nothing breaks.
SDv1 services: Create request naming rules to define endpoint names based on URL conditions. Go to the service in the Services app, open the Endpoints section, and select Configure request naming from the actions menu. Define a naming pattern using non-volatile placeholders (like {HTTP-Method}) combined with fixed path segments.
SDv2 services: Configure URL path pattern matching rules to derive stable endpoint names from raw URL paths.
The key to good manual rules: use non-volatile placeholders and fixed path segments. Avoid volatile placeholders like {URL} or {URL:Path} - they contain high-cardinality values that create a new endpoint for every unique URL.
Edit may 8, 2026 ⬇️
Correction on URL path in request naming rules
A clarification to the May 5 update above: {URL:Path} referenced directly in a request naming rule is not resolved — the path is dropped before endpoint detection, same as before. The May 5 description applies only when the placeholder is wrapped, not when used on its own.
To split by path through a naming rule: define a custom placeholder that references URL:Path, then use that custom placeholder in your rule. Resolution happens through the custom placeholder, not through {URL:Path} directly.
Why the extra step: a raw URL path is too volatile to expose as an endpoint name without thought. The custom-placeholder layer is where you transform the value — strip IDs, collapse variable segments, replace tokens with markers, before it reaches endpoint detection. The expectation is that you take care here, so the resulting endpoint name is not high-cardinality.
You can pass URL:Path through a custom placeholder unmodified. It will work. The cardinality cost — slow metric queries and a larger metrics bill — is then on you.
If you want path-based splitting without the cardinality risk: {URL:Path-Clean} remains the recommended option. It resolves directly in a naming rule, and Dynatrace replaces volatile values (IBANs, IP addresses, UUIDs, hex tokens) with stable markers before the path becomes the endpoint name.
Examples:
| Rule references | Resolves? | Endpoint name for /users/5f2b9a3c/profile |
|---|---|---|
{URL:Path} directly |
No — path dropped before endpoint detection | GET /* |
Custom placeholder wrapping URL:Path, unmodified |
Yes — but high cardinality is on you | GET /users/5f2b9a3c/profile |
Custom placeholder wrapping URL:Path, with your transformations |
Yes — you control the cardinality | GET /users/{id}/profile |
{URL:Path-Clean} |
Yes — Dynatrace normalizes volatile values | GET /users/UUID/profile |
End of May 8 edit ⬆️
Bug fix: OneAgent ~1.338
Separately from the normalization work, we found and fixed an issue where spans that did have http.route were sometimes still being generalized to /*. If you're seeing this behavior, updating OneAgent to version 1.338 or later will resolve it.
What's next
We're looking at K8s Gateway API, Envoy, and ingress gateway patterns specifically. Getting http.route right for these is a priority, since they're common in modern setups and have historically been a source of endpoint cardinality issues.
We're also planning to remove consumer span endpoint detection for SDv2. Today, message consumption spans are treated as endpoints alongside your HTTP server spans. This mixes message processing data into endpoint metrics like response time and throughput, making it harder to isolate the health of your service's actual entry points. Message consumption is already tracked separately in dedicated dt.service.messaging.* metrics, so removing consumer spans from endpoint detection gives you cleaner, more meaningful endpoint data. This change will apply to new tenants first; existing tenants keep their current behavior.
Beyond the heuristic rules, we're exploring more adaptive approaches to endpoint grouping - similar in concept to the pattern detection capabilities we're building for logs, where the system automatically identifies meaningful groups in unstructured data. No timeline to share, but endpoint quality is a continuous improvement area for us.
When these changes ship, release notes will explain what improved and why. We'll update this post as well.
Questions, feedback, or specific endpoint cases that aren't working well for you? Share them in this thread - it directly helps us prioritize.