Appearance
API Versioning
Canonical guide: API route contracts, implementation patterns, OpenAPI, gateway aggregation, migration, rollout, and troubleshooting.
Standard contract: versioned URLs such as
/api/v1/...and/api/v2/....Section: 15 - API
Last Updated: 2026-06-21
Quick Start
Every HTTP API service registers the shared web stack. This registers controllers, API Versioning, API Explorer, Swagger conventions, and the compatibility middleware when enabled:
csharp
builder.Services.AddSharedWeb(builder.Configuration, builder.Environment);
var app = builder.Build();
app.ConfigureSharedWeb(builder.Configuration);Use this standard service configuration:
json
{
"ApiVersioning": {
"EnableVersionedRoutePrefixConvention": true,
"EnableLegacyUnversionedRouteCompatibility": true
}
}New requests use a URL version:
text
GET /api/v1/orders/42
GET /api/v2/orders/42Do not put version strings in service startup code. Controllers use shared attributes and constants; package-owned minimal endpoints version themselves internally.
Direct Package Setup
Services that do not use AddSharedWeb can use the complete versioning package directly:
csharp
using Microtec.Web.ApiVersioning;
// Registers API Versioning, API Explorer, and version-aware Swagger.
builder.Services.AddMicrotecApiVersioning(builder.Configuration);
var app = builder.Build();
app.UseHttpsRedirection();
// Adds Swagger UI and temporary legacy /api/... compatibility before routing.
app.UseMicrotecApiVersioning(builder.Configuration);
app.UseRouting();AddMicrotecSwagger() remains an idempotent advanced method, but normal services do not call it separately.
Ownership and Responsibilities
| Package or component | Responsibility |
|---|---|
Microtec.Web.ApiVersioning | API-version options, route construction, controller conventions, ApiVersions constants, compatibility middleware, version-aware Swagger, and MapMicrotecVersionedGroup(...). |
Microtec.Web.Hosting | Shared-hosting convenience registration plus ERP-token and background-job endpoint mapping. |
| Endpoint packages | Map their own minimal endpoints through the shared versioned-group helper. Examples: Attachment, Templates, and Workflows. |
Application Program.cs | Registers and maps features. It does not construct versioned route strings or Swagger group names. |
| YARP Gateway | Aggregates the downstream Swagger document versions that each service declares. It does not rewrite arbitrary downstream API paths. |
The public C# namespace is Microtec.Web.ApiVersioning; the versioning and Swagger implementation no longer belongs to Microtec.Web.Hosting.
Infrastructure Services
InfrastructureServices follows the same contract; it does not define a second versioning model.
| Service | Versioned surface | Implementation |
|---|---|---|
| Attachments | Controller APIs and package-owned attachment endpoints | AddSharedWeb / ConfigureSharedWeb plus the shared v1 attachment mapper. |
| Notifications | Controller APIs and /api/v1/background-jobs/execute | Shared registration, the shared Swagger/compatibility pipeline, and MapBackgroundJobCallback(). |
| Templates | Controller APIs, including the legacy print paths | Shared minimal-web registration and pipeline. The legacy /Template/... aliases are rewritten before API-version compatibility resolves them to v1. |
Each service enables EnableVersionedRoutePrefixConvention and EnableLegacyUnversionedRouteCompatibility in its base appsettings.json. New clients call /api/v1/...; the previous unversioned API paths remain only as a temporary internal compatibility path.
Contract and Terminology
| Term | Meaning |
|---|---|
| API version | The compatibility contract exposed to a caller, such as v1 or v2. |
| Route version | The URL segment selected by the caller: /api/v{version}/.... |
| Swagger document group | The OpenAPI document name for a version, such as v1 or v2. |
| Default version | v1 (1.0) unless configuration changes it. |
| Breaking change | A change that can invalidate an existing caller. Add it in a new major API version instead of changing v1. |
| Legacy route | An old unversioned URL such as /api/orders/42. It may be temporarily rewritten to /api/v1/orders/42. |
The route version is the default and canonical reader. The header reader is disabled unless explicitly enabled for a controlled migration.
Version Lifecycle
- Add compatible behavior to the existing version when it does not change the contract.
- Add a
v2endpoint or controller when the contract changes incompatibly. - Keep
v1live while consumers migrate. - Publish and deploy the new service/package versions.
- Add
v2to the gateway endpoint configuration after the downstream Swagger document is available. - Deprecate and later remove
v1only through a separately approved lifecycle decision.
An endpoint is not automatically carried from v1 into v2. It is available only in the version groups or controller mappings where it is explicitly declared.
Internal Service Calls
Calls between Microtec services must use the canonical versioned URL and must not depend on legacy-route compatibility. Use ApiVersionRoutes.BuildV1(...) from Microtec.Web.ApiVersioning when a client has a service base URL:
csharp
var url = ApiVersionRoutes.BuildV1(
apiUrlOptions.Value.AttachmentServiceURL,
"Attachment/DownloadBase64Attachment/{attachmentId}");For a relative client route, compose it from ApiVersionRoutes.V1Prefix, for example ApiVersionRoutes.V1Prefix + "/Attachment". Service base URLs in ApiUrls remain host roots; the route helper owns the /api/v1 segment.
Complete Configuration Reference
All properties below bind from the ApiVersioning configuration section. Keep defaults unless there is a documented integration or migration need.
| Option | Default | Effect and usage |
|---|---|---|
Enabled | true | Master switch. Set false only for a service that must remain unversioned. |
DefaultMajorVersion | 1 | Major component of the default version. |
DefaultMinorVersion | 0 | Minor component of the default version. |
AssumeDefaultVersionWhenUnspecified | true | Uses the default when a version reader finds no version. It does not make an unversioned URL match a versioned route; use legacy compatibility for that. |
ReportApiVersions | true | Emits supported/deprecated API-version response headers when the API Versioning library can report them. |
EnableUrlSegmentReader | true | Reads the version from /api/v1/.... Keep enabled for the standard contract. |
EnableHeaderReader | false | Enables version selection from HeaderName. Use only during a documented migration. |
SubstituteApiVersionInUrl | true | Replaces the route token in generated OpenAPI paths, for example {version:apiVersion} becomes 1. |
EnableVersionedRoutePrefixConvention | false | Adds the versioned prefix to attribute-routed MVC controller templates. It does not change minimal endpoints. |
EnableLegacyUnversionedRouteCompatibility | false | Internally rewrites eligible legacy paths to the default version. It is not an HTTP redirect. |
RoutePrefix | api | Root API route segment. |
RouteVersionParameter | version | Name of the route token used for version selection. |
RouteConstraint | apiVersion | ASP.Versioning route-constraint name. |
HeaderName | X-Api-Version | Header read when EnableHeaderReader is enabled. |
SwaggerGroupNameFormat | 'v'VVV | API Explorer group format. It normally creates v1, v2, and so on. |
SwaggerSubstitutionFormat | VVV | Version format inserted into OpenAPI route paths. |
SwaggerRouteTemplate | /swagger/{documentName}/swagger.json | Route used to serve each generated OpenAPI document. |
SwaggerEndpointTemplate | ../swagger/{0}/swagger.json | Relative URL used by Swagger UI to load a document. |
SwaggerTitle | null | Optional title for all generated documents. The application assembly name is used when omitted. |
SwaggerDescription | null | Optional description for all generated documents. A default application description is used when omitted. |
LegacyRewriteExcludedPathPrefixes | /swagger, /health, /healthy, /metrics, /hangfire, /gateway/swagger | Paths that must never be rewritten by legacy compatibility. |
VersionedRouteConventionExcludedPathPrefixes | Same as LegacyRewriteExcludedPathPrefixes | Paths that must not be changed by the MVC route-prefix convention. |
Standard Configuration
json
{
"ApiVersioning": {
"Enabled": true,
"DefaultMajorVersion": 1,
"DefaultMinorVersion": 0,
"AssumeDefaultVersionWhenUnspecified": true,
"ReportApiVersions": true,
"EnableUrlSegmentReader": true,
"EnableHeaderReader": false,
"SubstituteApiVersionInUrl": true,
"EnableVersionedRoutePrefixConvention": true,
"EnableLegacyUnversionedRouteCompatibility": true
}
}Optional Reader Migration
Do not enable multiple readers for routine application development. When an external client cannot move immediately, enable the required reader deliberately and document its removal date:
json
{
"ApiVersioning": {
"EnableUrlSegmentReader": true,
"EnableHeaderReader": true,
"HeaderName": "X-Api-Version"
}
}The standard URL still remains /api/v1/...; a header reader is a temporary compatibility mechanism, not a replacement contract.
MVC Controllers
New v1 Controller
Use the standard ASP.Versioning attribute with the shared constant and route constant. This avoids local route or version strings:
csharp
using Asp.Versioning;
using Microtec.Web.ApiVersioning;
[ApiController]
[ApiVersion(ApiVersions.V1)]
[Route(MicrotecApiVersioningDefaults.VersionedControllerRoute)]
public sealed class OrdersController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult Get(Guid id) => Ok();
}The route is /api/v1/orders/{id} and Swagger places the operation in document v1.
Separate v1 and v2 Controllers
Use separate controllers when request/response types or behavior materially diverge:
csharp
using Asp.Versioning;
using Microtec.Web.ApiVersioning;
[ApiController]
[ApiVersion(ApiVersions.V1)]
[Route(MicrotecApiVersioningDefaults.VersionRoutePrefix + "/orders")]
public sealed class OrdersV1Controller : ControllerBase
{
[HttpGet("{id}")]
public IActionResult Get(Guid id) => Ok();
}
[ApiController]
[ApiVersion(ApiVersions.V2)]
[Route(MicrotecApiVersioningDefaults.VersionRoutePrefix + "/orders")]
public sealed class OrdersV2Controller : ControllerBase
{
[HttpGet("{id}")]
public IActionResult Get(Guid id) => Ok();
}The same logical resource can therefore evolve independently at /api/v1/orders/{id} and /api/v2/orders/{id}.
Version-Specific Actions in One Controller
Use action mappings when most endpoints are shared but a small number differ:
csharp
using Asp.Versioning;
using Microtec.Web.ApiVersioning;
[ApiController]
[ApiVersion(ApiVersions.V1)]
[ApiVersion(ApiVersions.V2)]
[Route(MicrotecApiVersioningDefaults.VersionRoutePrefix + "/orders")]
public sealed class OrdersController : ControllerBase
{
[HttpGet("summary")]
[MapToApiVersion(ApiVersions.V1)]
public IActionResult GetSummaryV1() => Ok();
[HttpGet("summary")]
[MapToApiVersion(ApiVersions.V2)]
public IActionResult GetSummaryV2() => Ok();
}ApiVersions contains compile-time constants for the common major contracts. Always use the standard ASP.Versioning attributes so the supported version is explicit at the controller and action.
v3 Controllers and Actions
Use the standard attributes in the same way for v3. It produces /api/v3/... routes and Swagger document v3:
csharp
using Asp.Versioning;
using Microtec.Web.ApiVersioning;
[ApiController]
[ApiVersion(ApiVersions.V3)]
[Route(MicrotecApiVersioningDefaults.VersionRoutePrefix + "/orders")]
public sealed class OrdersV3Controller : ControllerBase
{
[HttpGet("summary")]
[MapToApiVersion(ApiVersions.V3)]
public IActionResult GetSummary() => Ok();
}Service-Specific Minor Versions
Minor versions are owned by the service that introduces the contract. Define the compile-time constant once in that service, then use the standard attributes. This keeps the core package focused on shared major versions and prevents literals from spreading through endpoint code:
csharp
using Asp.Versioning;
using Microtec.Web.ApiVersioning;
public static class OrdersApiVersions
{
public const string V1_1 = "1.1";
}
[ApiController]
[ApiVersion(OrdersApiVersions.V1_1)]
[Route(MicrotecApiVersioningDefaults.VersionRoutePrefix + "/orders")]
public sealed class OrdersV1_1Controller : ControllerBase
{
}This produces /api/v1.1/... and Swagger document v1.1. For a minimal endpoint group, use the matching typed value: MicrotecApiVersions.Create(1, 1).
Existing Attribute-Routed Controllers
Controllers without explicit API-version metadata receive the default version through the shared controller convention. To migrate an existing [Route("api/...")] controller without changing every template:
- Enable
EnableVersionedRoutePrefixConvention. - Deploy the generated
/api/v1/...routes. - Enable
EnableLegacyUnversionedRouteCompatibilityonly while old callers still need/api/.... - Remove legacy compatibility after callers have moved.
The route convention applies to attribute-routed controller templates. Minimal endpoints need the versioned-group pattern below.
Version-Neutral Controllers
Use the standard ASP.Versioning ApiVersionNeutral metadata only for endpoints that genuinely have no API contract version, such as a narrowly scoped operational controller. Do not use version-neutral metadata to avoid a normal API migration.
Minimal Endpoints
Minimal APIs do not pass through MVC controller conventions. They must be mapped through MapMicrotecVersionedGroup(...), either directly in a package-owned mapper or through a built-in mapper that already uses it.
Built-In Mappers
Application startup remains simple:
csharp
app.MapAttachmentEndpoints();
app.MapTemplatesEndpoints();
app.MapInternalControllers();
app.MapBusinessOwnerEndpoints();
app.MapErpTokenEndpoints();
app.MapBackgroundJobCallback();| Mapper | Versioned route prefix | Default Swagger group |
|---|---|---|
MapAttachmentEndpoints() | /api/v1/attachments | v1 |
MapTemplatesEndpoints() | /api/v1/templates | v1 |
MapInternalControllers() | /api/v1/workflows | v1 |
MapBusinessOwnerEndpoints() | /api/v1/SideMenu | v1 |
MapErpTokenEndpoints() | /api/v1/erp | v1 |
MapBackgroundJobCallback() | /api/v1/background-jobs | v1 |
Do not pass BuildVersionedRoute(...), WithGroupName(...), or a manual Swagger group from Program.cs. The mapper owns route construction and Swagger grouping.
Built-In Mapper v2
The no-argument mappers remain v1. When an entire built-in mapper supports v2, pass a typed ApiVersion; Swagger derives v2 from that same value:
csharp
using Microtec.Web.ApiVersioning;
app.MapAttachmentEndpoints("attachments", MicrotecApiVersions.V2);Do not pass a string such as "v2" to select a Swagger document. The API version is the single source of truth for the route, endpoint metadata, and document group.
Custom v1 and v2 Groups
Declare route and version values once in the endpoint package. Map each endpoint only into the versions that support it:
csharp
using Asp.Versioning;
using Microtec.Web.ApiVersioning;
public static class OrdersEndpointRoutes
{
public const string Prefix = "orders";
public const string Tag = "Orders";
public const string GetById = "{id}";
public const string Recalculate = "{id}/recalculate";
public static readonly ApiVersion V1 = MicrotecApiVersions.V1;
public static readonly ApiVersion V2 = MicrotecApiVersions.V2;
}
public static class OrdersEndpointExtensions
{
public static IEndpointRouteBuilder MapOrdersEndpoints(
this IEndpointRouteBuilder app)
{
var v1 = app.MapMicrotecVersionedGroup(
OrdersEndpointRoutes.Prefix,
OrdersEndpointRoutes.Tag,
OrdersEndpointRoutes.V1);
v1.MapGet(OrdersEndpointRoutes.GetById, GetOrderV1);
var v2 = app.MapMicrotecVersionedGroup(
OrdersEndpointRoutes.Prefix,
OrdersEndpointRoutes.Tag,
OrdersEndpointRoutes.V2);
v2.MapGet(OrdersEndpointRoutes.GetById, GetOrderV2);
v2.MapPost(OrdersEndpointRoutes.Recalculate, RecalculateOrder);
return app;
}
}This produces:
| Operation | Route | Swagger document |
|---|---|---|
Read order v1 | GET /api/v1/orders/{id} | v1 |
Read order v2 | GET /api/v2/orders/{id} | v2 |
| Recalculate order | POST /api/v2/orders/{id}/recalculate | v2 only |
To keep an unchanged endpoint available in v2, map the same handler into the v2 group. To make an endpoint v2-only, do not map it into the v1 group.
Service startup remains unchanged:
csharp
app.MapOrdersEndpoints();What the Versioned Group Adds
MapMicrotecVersionedGroup(...) performs all of the following:
- Normalizes a prefix such as
ordersorapi/orders. - Creates
/api/v{version:apiVersion}/orderswhen versioning is enabled. - Registers the API-version set for the supplied version.
- Adds the endpoint tag.
- Assigns the matching Swagger group, such as
v1orv2.
When ApiVersioning:Enabled is false, it maps the supplied prefix without API-version metadata.
BuildVersionedRoute(...) returns a route template containing {version:apiVersion}. Use BuildVersionedPath(...) when code needs a concrete URL, such as an internal callback URL; it substitutes the configured default version and produces /api/v1/....
Swagger and OpenAPI
Generated Documents
With Swagger enabled, each discovered API version has its own document:
text
/swagger/v1/swagger.json
/swagger/v2/swagger.jsonSwagger UI is normally available at /swagger. EnableSwagger controls whether the shared Swagger middleware is enabled; it is separate from the ApiVersioning section.
How Operations Reach a Document
| Endpoint source | Swagger placement |
|---|---|
Controller marked [ApiVersion(ApiVersions.V1)] | v1 |
Controller marked [ApiVersion(ApiVersions.V2)] | v2 |
| Existing controller with no explicit metadata | Default document, normally v1 |
Minimal group created with V1 | v1 |
Minimal group created with V2 | v2 |
| Ungrouped API description | Default document only, normally v1 |
Ungrouped endpoints are intentionally not included in every version document. This prevents a default controller or endpoint from leaking into v2 simply because v2 exists.
Path Substitution
Source routes use the API-version route token:
text
/api/v{version:apiVersion}/orders/{id}When SubstituteApiVersionInUrl is true, the generated v1 OpenAPI document shows:
text
/api/v1/orders/{id}The version route parameter is removed from each operation after substitution, so Swagger UI does not ask callers to supply it separately.
Swagger Configuration Errors
| Symptom | Likely cause | Resolution |
|---|---|---|
Endpoint is missing from v2 | It was mapped only to v1 or the controller has no ApiVersion(ApiVersions.V2) metadata. | Add it to the v2 group or annotate/map the controller for v2. |
| Endpoint appears in the wrong version | Manual OpenAPI group metadata conflicts with version metadata. | Remove manual WithGroupName(...) usage and use the versioned-group helper. |
Swagger shows {version:apiVersion} | SubstituteApiVersionInUrl is disabled. | Restore the default true setting. |
| Swagger document is not served | EnableSwagger is false or the service does not call ConfigureSharedWeb. | Enable Swagger for the intended environment and use shared startup. |
See Swagger / OpenAPI for UI authentication and specification export.
Legacy Unversioned Route Migration
When enabled, compatibility middleware rewrites eligible requests inside the service:
text
Incoming: /api/orders/42
Handled as: /api/v1/orders/42It restores the original request path after the downstream pipeline finishes. It does not send a 301, 302, or other HTTP redirect to the caller.
Migration Procedure
- Enable
EnableVersionedRoutePrefixConventionand deploy/api/v1/...routes. - Verify the
v1Swagger document and versioned requests. - Enable
EnableLegacyUnversionedRouteCompatibilityonly when active clients still call legacy URLs. - Publish the date and route examples for clients to move to
/api/v1/.... - Observe legacy traffic and migrate callers.
- Disable compatibility once legacy callers are gone.
Do not add /swagger, health checks, metrics, Hangfire, gateway Swagger, or static files to the rewrite path. The default exclusion lists protect these routes; extend them only for a known non-API path.
Background Job Callbacks
MapBackgroundJobCallback() maps /api/v1/background-jobs/execute. When the REST background-job provider builds the callback URL, it uses BuildVersionedPath(...) with BackgroundJobs:CallbackPrefix. Keep the configuration value as the logical prefix:
json
{
"BackgroundJobs": {
"CallbackPrefix": "api/background-jobs"
}
}The generated callback URL is versioned automatically while API versioning is enabled. Do not hard-code v1 into CallbackPrefix.
Gateway Swagger Aggregation
The YARP gateway aggregates downstream Swagger documents. It does not create downstream API versions; it only exposes the versions declared by a service.
Service Entry
Use one SwaggerEndpoints entry per service and list only the versions the downstream service actually provides:
json
{
"ServiceName": "orders-apis",
"Url": "https://orders-apis.example.com/swagger/{version}/swagger.json",
"Name": "Orders API",
"Versions": [ "v1", "v2" ]
}The {version} token is replaced with the requested document version. A URL using /swagger/v1/swagger.json is also supported; the gateway replaces the v1 segment when another configured version is requested.
Gateway Defaults
json
{
"GatewaySwagger": {
"DefaultVersions": [ "v1" ],
"GatewayVersions": [ "v1" ],
"GatewayDisplayName": "Gateway API"
}
}| Setting | Meaning |
|---|---|
DefaultVersions | Used only when a service entry has no Versions array. Prefer explicit service versions. |
GatewayVersions | Versions of the gateway's own OpenAPI documents. Keep this separate from downstream service versions. |
GatewayDisplayName | Label used for the gateway's own Swagger UI entries. |
SwaggerEndpoints[].Versions | Downstream Swagger documents displayed for that service. Add v2 only after the downstream document is deployed. |
Gateway Swagger UI is served at /gateway/swagger. Aggregated downstream documents are fetched through:
text
/gateway/swagger/docs/{serviceName}/{version}/swagger.jsonThe gateway injects the configured downstream API key and rewrites downstream paths so Swagger UI requests travel back through the gateway. If a service is offline, the gateway returns a valid placeholder document instead of breaking the entire Swagger UI.
Keep these gateway settings disabled because the gateway proxies arbitrary downstream paths:
json
{
"ApiVersioning": {
"EnableVersionedRoutePrefixConvention": false,
"EnableLegacyUnversionedRouteCompatibility": false
}
}See Gateway Routes for the YARP path rules.
Testing Checklist
Every versioned change should prove both routing and documentation behavior.
| Check | Expected result |
|---|---|
GET /api/v1/... | The intended v1 endpoint responds. |
GET /api/v2/... | The intended v2 endpoint responds only when it has been mapped or annotated for v2. |
v2-only operation through v1 | It is unavailable and absent from Swagger v1. |
/swagger/v1/swagger.json | Contains v1 operations and concrete /api/v1/... paths. |
/swagger/v2/swagger.json | Contains only v2 operations and concrete /api/v2/... paths. |
Legacy /api/... request during migration | Reaches the equivalent default-version route only when compatibility is enabled. |
| Background-job REST callback | Uses /api/v1/background-jobs/execute while versioning is enabled. |
Gateway Swagger v2 entry | Loads only after the downstream service has deployed /swagger/v2/swagger.json. |
For minimal endpoints, assert both the route and the Swagger group. A route can look versioned while being placed in the wrong document if custom mapping bypasses MapMicrotecVersionedGroup(...).
Publishing and Rollout
When a versioning-core change affects endpoint mapping, publish the changed packages together so their shared version metadata stays aligned:
| Package | Publish when |
|---|---|
Microtec.Web.ApiVersioning | Route/group helper or option defaults change. |
Microtec.Web.Hosting | Registration, controller conventions, compatibility, Swagger, ERP tokens, or background callbacks change. |
Microtec.Attachment.Client | Attachment endpoint mapper changes. |
Microtec.Templates.Client | Template endpoint mapper changes. |
Microtec.PublicApi | Workflow endpoint mapper changes. |
After publishing, update the affected central package versions in Platforms and InfrastructureServices, restore, build, and deploy downstream services before adding their new version to gateway SwaggerEndpoints.
The Microtec.Web.ApiVersioning package is transitive for normal service applications; applications do not need to add it directly unless they intentionally use the low-level route/group API outside an existing package mapper.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Route is still /api/... | MVC route convention is disabled, or a minimal endpoint bypasses the shared group helper. | Enable the MVC convention for attribute-routed controllers; use MapMicrotecVersionedGroup(...) inside minimal mappers. |
Route looks like /api/{version}1/... | The route template concatenated a literal version incorrectly. | Use MicrotecApiVersioningDefaults.VersionRoutePrefix or the shared group helper. |
New operation is visible in v1 and v2 | Controller/action was explicitly mapped to both, or custom group metadata is incorrect. | Keep the operation only in its intended version mapping; remove manual group metadata. |
New operation is missing from v2 | It was never mapped/annotated for v2. | Add an ApiVersion(ApiVersions.V2) controller/action mapping or map it in a V2 minimal group. |
| Old client receives a route failure | The client still calls /api/... after compatibility has been disabled. | Move the client to /api/v1/..., or temporarily re-enable compatibility with an explicit removal plan. |
| Swagger shows an unversioned path token | Version substitution is disabled. | Set SubstituteApiVersionInUrl to true. |
| Gateway cannot load a document | The service is offline, its port/URL is wrong, or Versions declares a document the service has not deployed. | Verify downstream connectivity and Swagger URL, then make Versions match the deployed documents. |
| Background worker callback fails | The service is using an outdated Hosting package or callback base URL is incorrect. | Update Microtec.Web.Hosting, keep CallbackPrefix logical, and verify the service base URL. |