diff --git a/.github/workflows/apicompat.yml b/.github/workflows/apicompat.yml
new file mode 100644
index 000000000..8ead46507
--- /dev/null
+++ b/.github/workflows/apicompat.yml
@@ -0,0 +1,30 @@
+name: API Compatibility
+
+on:
+ pull_request: {}
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: read
+
+jobs:
+ apicompat:
+ name: Check TFM API Compatibility
+ runs-on: ubuntu-latest
+ env:
+ DOTNET_CLI_TELEMETRY_OPTOUT: 1
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
+ NUGET_XMLDOC_MODE: skip
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup dotnet
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.x
+
+ - name: Run API Compatibility Check
+ run: bash scripts/apicompat.sh --build
diff --git a/NATS.Net.sln b/NATS.Net.sln
index 20b706cc4..bea40d882 100644
--- a/NATS.Net.sln
+++ b/NATS.Net.sln
@@ -103,6 +103,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\test_linux.yml = .github\workflows\test_linux.yml
.github\workflows\test_linux_core.yml = .github\workflows\test_linux_core.yml
.github\workflows\test_windows.yml = .github\workflows\test_windows.yml
+ .github\workflows\apicompat.yml = .github\workflows\apicompat.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{5BF3C0C5-94CA-4008-8DF5-890ED8E06990}"
diff --git a/apicompat.suppression.xml b/apicompat.suppression.xml
new file mode 100644
index 000000000..881f5df51
--- /dev/null
+++ b/apicompat.suppression.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ CP0001
+ T:NATS.Client.Core.SslClientAuthenticationOptions
+
+
+
+ CP0002
+ M:NATS.Client.Core.NatsTlsOpts.get_ConfigureClientAuthentication
+
+
+
+ CP0002
+ M:NATS.Client.Hosting.NatsHostingExtensions.AddNats(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.Int32,System.Func{NATS.Client.Core.NatsOpts,NATS.Client.Core.NatsOpts},System.Action{NATS.Client.Core.NatsConnection})
+
+
diff --git a/scripts/apicompat.sh b/scripts/apicompat.sh
new file mode 100644
index 000000000..aa64f3576
--- /dev/null
+++ b/scripts/apicompat.sh
@@ -0,0 +1,76 @@
+#!/bin/bash
+# API Compatibility Check Script
+# Verifies that APIs are compatible across different TFMs
+# Usage: ./scripts/apicompat.sh [--build]
+
+set -e
+
+# Get script directory (works on both Windows and Linux)
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+
+PROJECTS="NATS.Client.Abstractions NATS.Client.Core NATS.Client.Hosting NATS.Client.JetStream NATS.Client.KeyValueStore NATS.Client.ObjectStore NATS.Client.Services NATS.Client.Serializers.Json NATS.Client.Simplified NATS.Net NATS.Extensions.Microsoft.DependencyInjection"
+
+SUPPRESSION_FILE="$ROOT_DIR/apicompat.suppression.xml"
+
+# Check if apicompat is installed
+if ! command -v apicompat &> /dev/null; then
+ echo "Installing Microsoft.DotNet.ApiCompat.Tool..."
+ dotnet tool install --global Microsoft.DotNet.ApiCompat.Tool
+fi
+
+# Build if requested
+if [ "$1" = "--build" ]; then
+ echo "Building solution..."
+ dotnet build -c Release "$ROOT_DIR/NATS.Net.sln"
+fi
+
+check_compat() {
+ local baseline_tfm=$1
+ local target_tfm=$2
+ local failed=0
+
+ echo ""
+ echo "=============================================="
+ echo "Checking: $baseline_tfm (baseline) vs $target_tfm"
+ echo "=============================================="
+
+ for project in $PROJECTS; do
+ left="$ROOT_DIR/src/$project/bin/Release/$baseline_tfm/$project.dll"
+ right="$ROOT_DIR/src/$project/bin/Release/$target_tfm/$project.dll"
+
+ if [ -f "$left" ] && [ -f "$right" ]; then
+ echo -n " $project: "
+ if apicompat --left "$left" --right "$right" --suppression-file "$SUPPRESSION_FILE" --permit-unnecessary-suppressions > /dev/null 2>&1; then
+ echo "OK"
+ else
+ echo "FAILED"
+ apicompat --left "$left" --right "$right" --suppression-file "$SUPPRESSION_FILE" --permit-unnecessary-suppressions 2>&1 | head -20
+ failed=1
+ fi
+ else
+ echo " $project: SKIPPED (assemblies not found)"
+ fi
+ done
+
+ return $failed
+}
+
+echo "API Compatibility Check"
+echo "======================="
+
+exit_code=0
+
+# Only check against net8.0 as the target TFM
+# net6.0 has known differences with init accessors
+check_compat "netstandard2.0" "net8.0" || exit_code=1
+check_compat "netstandard2.1" "net8.0" || exit_code=1
+
+echo ""
+if [ $exit_code -eq 0 ]; then
+ echo "All API compatibility checks passed!"
+else
+ echo "Some API compatibility checks failed!"
+fi
+
+exit $exit_code
diff --git a/tools/site_src/documentation/advanced/intro.md b/tools/site_src/documentation/advanced/intro.md
index 4e6ff7e55..ce111115b 100644
--- a/tools/site_src/documentation/advanced/intro.md
+++ b/tools/site_src/documentation/advanced/intro.md
@@ -95,3 +95,4 @@ essentially sent back to back after they're picked up from internal queues and b
- [Serialization](serialization.md) is the process of converting an object into a format that can be stored or transmitted.
- [Security](security.md) is an important aspect of any distributed system. NATS provides a number of security features to help you secure your applications.
- [AOT Deployment](aot.md) is a way to deploy your applications as native platform executables, which produces faster startup times and better performance in most cases.
+- [Platform Compatibility](platform-compatibility.md) documents API differences across target frameworks (.NET Standard, .NET 6, .NET 8).
diff --git a/tools/site_src/documentation/advanced/platform-compatibility.md b/tools/site_src/documentation/advanced/platform-compatibility.md
new file mode 100644
index 000000000..602512cc8
--- /dev/null
+++ b/tools/site_src/documentation/advanced/platform-compatibility.md
@@ -0,0 +1,93 @@
+# Platform Compatibility
+
+NATS.Net targets multiple .NET platforms to provide broad compatibility:
+
+- `netstandard2.0` - .NET Framework 4.6.1+, .NET Core 2.0+, Mono, Xamarin, Unity
+- `netstandard2.1` - .NET Core 3.0+
+- `net6.0` - .NET 6
+- `net8.0` - .NET 8
+
+While the API surface is designed to be consistent across all target frameworks, there are some
+intentional differences due to platform capabilities. This page documents these differences.
+
+## TLS Configuration
+
+### SslClientAuthenticationOptions
+
+The [`NatsTlsOpts.ConfigureClientAuthentication`](xref:NATS.Client.Core.NatsTlsOpts.ConfigureClientAuthentication)
+property allows you to configure TLS client authentication options.
+
+| Target Framework | Type |
+|-----------------|------|
+| `netstandard2.0` | `NATS.Client.Core.SslClientAuthenticationOptions` (polyfill) |
+| `netstandard2.1`, `net6.0`, `net8.0` | `System.Net.Security.SslClientAuthenticationOptions` (BCL) |
+
+On `netstandard2.0`, the library provides a polyfill type `NATS.Client.Core.SslClientAuthenticationOptions`
+because the BCL type doesn't exist in that target framework. The polyfill provides a subset of the
+properties available in the BCL type:
+
+- `TargetHost`
+- `EnabledSslProtocols`
+- `ClientCertificates`
+- `CertificateRevocationCheckMode`
+- `RemoteCertificateValidationCallback`
+- `LocalCertificateSelectionCallback`
+
+If you need the full `SslClientAuthenticationOptions` functionality, consider targeting `netstandard2.1` or later.
+
+## Dependency Injection
+
+### Keyed Services
+
+The [`AddNats`](xref:NATS.Client.Hosting.NatsHostingExtensions.AddNats*) extension method has different
+signatures depending on the target framework:
+
+**netstandard2.0, netstandard2.1, net6.0:**
+```csharp
+public static IServiceCollection AddNats(
+ this IServiceCollection services,
+ int poolSize = 1,
+ Func? configureOpts = null,
+ Action? configureConnection = null)
+```
+
+**net8.0:**
+```csharp
+public static IServiceCollection AddNats(
+ this IServiceCollection services,
+ int poolSize = 1,
+ Func? configureOpts = null,
+ Action? configureConnection = null,
+ object? key = null) // Additional parameter for keyed services
+```
+
+[Keyed dependency injection services](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#keyed-services)
+were introduced in .NET 8. The `key` parameter allows you to register multiple NATS connections
+with different keys:
+
+```csharp
+// .NET 8+ only
+services.AddNats(key: "primary", configureOpts: opts => opts with { Url = "nats://primary:4222" });
+services.AddNats(key: "secondary", configureOpts: opts => opts with { Url = "nats://secondary:4222" });
+
+// Inject with [FromKeyedServices("primary")]
+public class MyService([FromKeyedServices("primary")] INatsConnection primaryNats) { }
+```
+
+## API Compatibility Checking
+
+The repository includes an API compatibility check that runs in CI to ensure APIs remain consistent
+across target frameworks. Known intentional differences are documented in `apicompat.suppression.xml`
+at the repository root.
+
+To run the compatibility check locally:
+
+```bash
+./scripts/apicompat.sh --build
+```
+
+## What's Next
+
+- [Serialization](serialization.md) is the process of converting an object into a format that can be stored or transmitted.
+- [Security](security.md) is an important aspect of any distributed system. NATS provides a number of security features to help you secure your applications.
+- [AOT Deployment](aot.md) is a way to deploy your applications as native platform executables.
diff --git a/tools/site_src/documentation/toc.yml b/tools/site_src/documentation/toc.yml
index fb81e4203..481d7f7b0 100644
--- a/tools/site_src/documentation/toc.yml
+++ b/tools/site_src/documentation/toc.yml
@@ -42,6 +42,8 @@
href: advanced/security.md
- name: AOT Deployments
href: advanced/aot.md
+ - name: Platform Compatibility
+ href: advanced/platform-compatibility.md
- name: Updating Documentation
href: update-docs.md