diff --git a/docs/docs/httprequestbuilder.md b/docs/docs/httprequestbuilder.md
index 63a5e34..b9df141 100644
--- a/docs/docs/httprequestbuilder.md
+++ b/docs/docs/httprequestbuilder.md
@@ -14,6 +14,20 @@ At a high level, the workflow looks like this:
This keeps each request self-contained and avoids mutating shared `HttpClient` state like `DefaultRequestHeaders`.
+:::danger[BaseAddress Trailing Slash]
+
+When you build a request with a relative route, `HttpClient.BaseAddress` must end with a trailing slash for the route to be appended to it correctly. This is standard `Uri` combination behavior (RFC 3986 relative reference resolution), not something FluentHttpClient controls.
+
+A base address of `https://example.com/api/posts` combined with the route `1` resolves to `https://example.com/api/1`, dropping the `posts` segment, because the base address has no trailing slash. Use `https://example.com/api/posts/` instead to get `https://example.com/api/posts/1`.
+
+Adding a leading slash to the route does not fix this either. A route of `/1` is treated as an absolute path and replaces the entire base address path, giving you `https://example.com/1` instead, dropping `api` and `posts` both.
+
+This only applies when building a request from a relative route. `UsingBase` does not accept or set a route, so it is not affected.
+
+**See: https://www.rfc-editor.org/info/rfc3986/#section-5**
+
+:::
+
## Fluent Workflow
FluentHttpClient is built around a simple pattern:
diff --git a/src/FluentHttpClient/HttpClientExtensions.cs b/src/FluentHttpClient/HttpClientExtensions.cs
index b3a70ef..0c422fc 100644
--- a/src/FluentHttpClient/HttpClientExtensions.cs
+++ b/src/FluentHttpClient/HttpClientExtensions.cs
@@ -14,6 +14,13 @@ public static class HttpClientExtensions
///
/// The instance to use for sending requests.
/// A new instance initialized with the client's base address.
+ ///
+ /// This method does not accept or set a route, and this library gives you no public way to add one
+ /// afterward. Because of that, the resulting request always targets
+ /// as-is, so the trailing slash requirement described on UsingRoute does not apply here. It only
+ /// matters for derived builder types that introduce their own route, since they inherit the same
+ /// combination behavior.
+ ///
public static HttpRequestBuilder UsingBase(this HttpClient client)
{
return new HttpRequestBuilder(client);
@@ -26,6 +33,13 @@ public static HttpRequestBuilder UsingBase(this HttpClient client)
/// The type of the request builder to create.
/// The instance to use for sending requests.
/// A new instance of initialized with the client's base address.
+ ///
+ /// This method does not accept or set a route, and this library gives you no public way to add one
+ /// afterward. Because of that, the resulting request always targets
+ /// as-is, so the trailing slash requirement described on UsingRoute does not apply here. It only
+ /// matters for derived builder types that introduce their own route, since they inherit the same
+ /// combination behavior.
+ ///
#if NET7_0_OR_GREATER
[RequiresDynamicCode("Constructs TBuilder with Activator.CreateInstance, which can require runtime code generation. For Native AOT, use the UsingRoute overload that takes a factory delegate.")]
#endif
@@ -45,6 +59,17 @@ public static TBuilder UsingBase(this HttpClient client)
/// The instance to use for sending requests.
/// The route string for the request URI, which can be absolute or relative.
/// A new instance initialized with the specified route.
+ ///
+ /// When the route is relative, must end with a trailing slash
+ /// for the route to be appended to it correctly. This is standard combination
+ /// behavior (RFC 3986 relative reference resolution), not something this library controls. For
+ /// example, a base address of https://example.com/api/posts combined with the route 1
+ /// resolves to https://example.com/api/1, dropping the posts segment, because the base
+ /// address has no trailing slash. Use https://example.com/api/posts/ instead to get
+ /// https://example.com/api/posts/1. Adding a leading slash to the route does not fix this
+ /// either. A route of /1 is treated as an absolute path and replaces the entire base address
+ /// path, giving you https://example.com/1 instead, dropping api and posts both.
+ ///
public static HttpRequestBuilder UsingRoute(this HttpClient client, string route)
{
return new HttpRequestBuilder(client, route);
@@ -58,6 +83,17 @@ public static HttpRequestBuilder UsingRoute(this HttpClient client, string route
/// The instance to use for sending requests.
/// The route string for the request URI, which can be absolute or relative.
/// A new instance of initialized with the specified route.
+ ///
+ /// When the route is relative, must end with a trailing slash
+ /// for the route to be appended to it correctly. This is standard combination
+ /// behavior (RFC 3986 relative reference resolution), not something this library controls. For
+ /// example, a base address of https://example.com/api/posts combined with the route 1
+ /// resolves to https://example.com/api/1, dropping the posts segment, because the base
+ /// address has no trailing slash. Use https://example.com/api/posts/ instead to get
+ /// https://example.com/api/posts/1. Adding a leading slash to the route does not fix this
+ /// either. A route of /1 is treated as an absolute path and replaces the entire base address
+ /// path, giving you https://example.com/1 instead, dropping api and posts both.
+ ///
#if NET7_0_OR_GREATER
[RequiresDynamicCode("Constructs TBuilder with Activator.CreateInstance, which can require runtime code generation. For Native AOT, use the UsingRoute overload that takes a factory delegate.")]
#endif
@@ -77,6 +113,17 @@ public static TBuilder UsingRoute(this HttpClient client, string route
/// The instance to use for sending requests.
/// The URI for the request.
/// A new instance initialized with the specified URI.
+ ///
+ /// When the route is relative, must end with a trailing slash
+ /// for the route to be appended to it correctly. This is standard combination
+ /// behavior (RFC 3986 relative reference resolution), not something this library controls. For
+ /// example, a base address of https://example.com/api/posts combined with the route 1
+ /// resolves to https://example.com/api/1, dropping the posts segment, because the base
+ /// address has no trailing slash. Use https://example.com/api/posts/ instead to get
+ /// https://example.com/api/posts/1. Adding a leading slash to the route does not fix this
+ /// either. A route of /1 is treated as an absolute path and replaces the entire base address
+ /// path, giving you https://example.com/1 instead, dropping api and posts both.
+ ///
public static HttpRequestBuilder UsingRoute(this HttpClient client, Uri uri)
{
return new HttpRequestBuilder(client, uri);
@@ -89,6 +136,17 @@ public static HttpRequestBuilder UsingRoute(this HttpClient client, Uri uri)
/// The instance to use for sending requests.
/// The URI for the request.
/// A new instance of initialized with the specified URI.
+ ///
+ /// When the route is relative, must end with a trailing slash
+ /// for the route to be appended to it correctly. This is standard combination
+ /// behavior (RFC 3986 relative reference resolution), not something this library controls. For
+ /// example, a base address of https://example.com/api/posts combined with the route 1
+ /// resolves to https://example.com/api/1, dropping the posts segment, because the base
+ /// address has no trailing slash. Use https://example.com/api/posts/ instead to get
+ /// https://example.com/api/posts/1. Adding a leading slash to the route does not fix this
+ /// either. A route of /1 is treated as an absolute path and replaces the entire base address
+ /// path, giving you https://example.com/1 instead, dropping api and posts both.
+ ///
#if NET7_0_OR_GREATER
[RequiresDynamicCode("Constructs TBuilder with Activator.CreateInstance, which can require runtime code generation. For Native AOT, use the UsingRoute overload that takes a factory delegate.")]
#endif