---
title: SAIF.Web.API.Clients.35 business rules
summary: Enumerated business rules with per-rule trigger, enforcement, and outcomes.
status: draft
owner: bendy
last_reviewed: 2026-09-15
library: SAIF.Web.API.Clients.35
reference_type: package
---

# SAIF.Web.API.Clients.35 business rules

This page captures the framework/behavioral contract rules enforced by this library. `SAIF.Web.API.Clients.35` is an infrastructure library; the rules below govern HTTP client behavior (identity propagation, authentication, retry, serialization, and error translation), not SAIF insurance-domain business logic. No domain-specific rule was found in the compiled scope. All rules apply identically to the linked source shared with `SAIF.Web.API.Clients`, subject to the dormant/retired build status described in [Overview](overview.md).

## BR-01 Identity headers on every request

| Field | Value |
| --- | --- |
| **Statement** | Every outbound request must identify the calling machine, application, and user. |
| **Trigger** | Any request built. |
| **Enforcement** | `JsonHttpClient.GetJsonResponse` adds `Client-Name`, `Application-ID`, `Client-User` headers from `ClientInfo`. |
| **Outcome** | The three identity headers are present on every outbound request. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs, src/SAIF.Web.API.Clients/ClientInfo.cs |

---

## BR-02 Default NTLM authentication

| Field | Value |
| --- | --- |
| **Statement** | Requests authenticate with NTLM unless the caller supplies an authenticator. |
| **Trigger** | Client construction. |
| **Enforcement** | `JsonHttpClient` constructor: `_authenticator = authenticator ?? new NtlmAuthenticator()`. |
| **Outcome** | `RestClient.Authenticator` is set to the supplied authenticator, or `NtlmAuthenticator` by default. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

---

## BR-03 Conditional timeout override

| Field | Value |
| --- | --- |
| **Statement** | A timeout override is applied only when greater than zero. |
| **Trigger** | Client creation. |
| **Enforcement** | `JsonHttpClient.GetJsonResponse`: `if (TimeoutOverride > 0)`. |
| **Outcome** | `client.Timeout` is set when `TimeoutOverride > 0`; otherwise the RestSharp default applies. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

---

## BR-04 On-behalf-of header forwarding

| Field | Value |
| --- | --- |
| **Statement** | When acting on behalf of an interactive user, the domainless user name is forwarded. |
| **Trigger** | `SetOnBehalfOfUserHeaders == true`. |
| **Enforcement** | `JsonHttpClient.GetJsonResponse` adds an `OnBehalfOfUser` header sourced from `HttpContext.Current.DomainlessUser()`. |
| **Outcome** | The `OnBehalfOfUser` header is added to the request. In this .35 project, `Extensions/HttpContextExtensions.cs` is excluded from compilation and `System.Web` is not referenced, so this path has no compilable implementation here.* |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs, src/SAIF.Web.API.Clients/Extensions/HttpContextExtensions.cs (excluded from this project) |

---

## BR-05 Transient failure retry

| Field | Value |
| --- | --- |
| **Statement** | Transient failures are retried up to 3 times with a delay. |
| **Trigger** | Response status is in `RetryOnStatusCodes` (default: transport failure `0`, 502, 503, 401) and `retries > 0`. |
| **Enforcement** | `JsonHttpClient.GetJsonResponse`: `Thread.Sleep(RetryDelayInMs)` then recursive re-issue. |
| **Outcome** | Up to 3 retry attempts occur before the response is passed to error handling. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

---

## BR-06 Unsuccessful response raises typed exception

| Field | Value |
| --- | --- |
| **Statement** | Unsuccessful responses raise typed exceptions unless suppressed. |
| **Trigger** | `EnsureSuccessfulResponse == true`. |
| **Enforcement** | `JsonHttpClient.GetJsonResponse` calls `ExceptionHandler.EnsureSuccessfulResponse(response)`. |
| **Outcome** | A typed `SAIFException` subtype is thrown for non-success responses. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs, src/SAIF.Web.API.Clients/ExceptionHandler.cs |

---

## BR-07 Configured DateTimeZoneHandling applied to serialization

| Field | Value |
| --- | --- |
| **Statement** | Request bodies serialize with the client's configured `DateTimeZoneHandling`. |
| **Trigger** | Request build. |
| **Enforcement** | `JsonHttpClient.GetJsonResponse` / `JsonSerializer` constructor configure the serializer per instance. |
| **Outcome** | The serializer instance honors the client's `DateTimeZoneHandling` setting for the request. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs, src/SAIF.Web.API.Clients/JsonSerializer.cs |

---

## BR-08 Array deserialization normalization

| Field | Value |
| --- | --- |
| **Statement** | JSON arrays deserialize to `List<T>`; single objects deserialize to a one-element list. |
| **Trigger** | Payload deserialization. |
| **Enforcement** | `JsonHttpClient.GetContentArray<T>`. |
| **Outcome** | `ApiResponse<T>.Results` is populated as a normalized list regardless of whether the payload was a JSON array or a single object. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

---

## BR-09 String deserialization failure fallback

| Field | Value |
| --- | --- |
| **Statement** | Deserialization failures for `T = string` return the exception message as the result instead of throwing. |
| **Trigger** | `catch (Exception ex)` in `GetContentArray<T>`. |
| **Enforcement** | `JsonHttpClient.GetContentArray<T>`. |
| **Outcome** | `ex.Message` becomes the single result for `T = string`; for other `T` the result array is empty. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

---

## BR-10 Server name from response header

| Field | Value |
| --- | --- |
| **Statement** | The responding server name is taken from the `Server-Name` response header. |
| **Trigger** | Envelope construction. |
| **Enforcement** | `JsonHttpClient.GetApiResponse<T>`. |
| **Outcome** | `ApiResponse.Server` is set from the first header named `Server-Name`; null when the header is absent. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

---

## BR-11 String result unquoting

| Field | Value |
| --- | --- |
| **Statement** | A `string` result is returned with surrounding double quotes stripped. |
| **Trigger** | `T == string`. |
| **Enforcement** | `JsonHttpClient.GetResult<T>`. |
| **Outcome** | The caller receives the unquoted string content. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

---

## BR-12 Primitive result conversion

| Field | Value |
| --- | --- |
| **Statement** | Primitive results convert directly from raw content. |
| **Trigger** | `typeof(T).IsPrimitive`. |
| **Enforcement** | `JsonHttpClient.GetResult<T>` calls `Convert.ChangeType(content, T)`. |
| **Outcome** | The caller receives the converted primitive value. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

---

## BR-13 Byte array Base64 decoding

| Field | Value |
| --- | --- |
| **Statement** | `byte[]` results are Base64-decoded from the quoted content. |
| **Trigger** | `T == byte[]`. |
| **Enforcement** | `JsonHttpClient.GetResult<T>` calls `Convert.FromBase64String(content.Trim('"'))`. |
| **Outcome** | The caller receives the decoded byte array. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

---

## BR-14 Null payload default result

| Field | Value |
| --- | --- |
| **Statement** | A null or literal `"null"` payload yields `default(T)`. |
| **Trigger** | Content is null or the literal string `"null"`. |
| **Enforcement** | `JsonHttpClient.GetResult<T>`. |
| **Outcome** | The caller receives `default(T)`. |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

---

## BR-15 Cast failure default result

| Field | Value |
| --- | --- |
| **Statement** | A cast failure on a complex result yields `default(T)` rather than an exception. |
| **Trigger** | `InvalidCastException` thrown while extracting the first element of the deserialized content array. |
| **Enforcement** | `JsonHttpClient.GetResult<T>`. |
| **Outcome** | The caller receives `default(T)` instead of an unhandled exception. An empty result array raises `IndexOutOfRangeException` instead, since only `InvalidCastException` is caught.* |
| **Source** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

---

## BR-16 Success status range

| Field | Value |
| --- | --- |
| **Statement** | Only status codes in 200–399 are treated as success. |
| **Trigger** | Any response. |
| **Enforcement** | `ExceptionHandler.EnsureSuccessfulResponse`. |
| **Outcome** | Status codes outside 200–399 proceed to error mapping; others pass through. |
| **Source** | src/SAIF.Web.API.Clients/ExceptionHandler.cs |

---

## BR-17 Error message source selection

| Field | Value |
| --- | --- |
| **Statement** | Error text is sourced from the response body when present. When the body is blank, `GetContent`'s nested condition checks do not implement a simple "prefer `ErrorMessage`, else `ErrorException`, else status line" precedence: a nonblank `ErrorMessage` with a null `ErrorException` falls through to an empty string; a blank `ErrorMessage` with a non-null `ErrorException` returns the blank `ErrorMessage` (not the exception text); only when both `ErrorMessage` is nonblank and `ErrorException` is non-null does the method return `ErrorException.Message`. The synthesized status line is used only when both `ErrorMessage` and `ErrorException` are absent and the status is not 400. |
| **Trigger** | Error mapping, when `response.Content` is null/whitespace. |
| **Enforcement** | `ExceptionHandler.GetContent`: `if (!Content.IsNullOrWhiteSpace()) return Content; else if (!ErrorMessage.IsNullOrWhiteSpace() \|\| ErrorException != null) { if (ErrorMessage.IsNullOrWhiteSpace()) return ErrorMessage; else if (ErrorException != null) return ErrorException.Message; } else if (StatusCode != BadRequest) return "{code} - {StatusDescription}"; return string.Empty;` using `SAIF.Common.Extensions.IsNullOrWhiteSpace` from `SAIF.Framework.Common`. |
| **Outcome** | Body present → body text. Body blank, `ErrorMessage` nonblank + `ErrorException` null → `string.Empty` (not `ErrorMessage`). Body blank, `ErrorMessage` blank + `ErrorException` non-null → blank `ErrorMessage` (not `ErrorException.Message`). Body blank, both present → `ErrorException.Message`. Body blank, both absent → status line unless status is 400, otherwise `string.Empty`. |
| **Constraint/defect** | The inner branch order in `GetContent` appears inverted relative to its evident intent (prefer `ErrorMessage`, else fall back to `ErrorException.Message`): as written, a lone nonblank `ErrorMessage` is discarded and a lone `ErrorException` is also discarded in favor of the blank `ErrorMessage`. This is documented as observed, verified behavior of the current implementation, not its apparent designed intent. |
| **Source** | src/SAIF.Web.API.Clients/ExceptionHandler.cs |

---

## BR-18 Recognized error property names

| Field | Value |
| --- | --- |
| **Statement** | Recognized error property names are `errormessage`, `exceptionmessage`, `message`, `usermessage` (case-insensitive). |
| **Trigger** | Response body parses as JSON. |
| **Enforcement** | `ExceptionHandler.ToKnownError` via `JToken.Parse(content)`. |
| **Outcome** | Matching property values are accumulated into the extracted error message. |
| **Source** | src/SAIF.Web.API.Clients/ExceptionHandler.cs |

---

## BR-19 Fallback to raw body on parse failure

| Field | Value |
| --- | --- |
| **Statement** | Unparseable or empty structured errors fall back to the raw body. |
| **Trigger** | JSON parsing throws, or the accumulated error-message builder is blank. |
| **Enforcement** | `ExceptionHandler.ToKnownError`. |
| **Outcome** | The raw response content becomes the error message. |
| **Source** | src/SAIF.Web.API.Clients/ExceptionHandler.cs |

---

## BR-20 HTTP status to exception type mapping

| Field | Value |
| --- | --- |
| **Statement** | HTTP status maps to exception type: 404 → `NoMatchingDataException`, 401/403 → `UnauthorizedAccessException`, 400 → `InvalidDataException`, all others → `InternalServerError`. |
| **Trigger** | Error mapping. |
| **Enforcement** | `ExceptionHandler.ToKnownError`, constructing types from `SAIF.Web.API.Core`'s `SAIF.API.Exceptions.SAIFException` hierarchy. |
| **Outcome** | A typed exception is thrown with `HttpStatusCode` populated. |
| **Source** | src/SAIF.Web.API.Clients/ExceptionHandler.cs, src/SAIF.Web.API.Core/SAIFException.cs |

---

## BR-21 Serialization member and value handling

| Field | Value |
| --- | --- |
| **Statement** | Serialization ignores unknown members and includes nulls and default values. |
| **Trigger** | Request body serialization. |
| **Enforcement** | `JsonSerializer` constructor applies `MissingMemberHandling.Ignore`, `NullValueHandling.Include`, `DefaultValueHandling.Include`. |
| **Outcome** | Newtonsoft settings are applied consistently to every serialized request body. |
| **Source** | src/SAIF.Web.API.Clients/JsonSerializer.cs |

---

## Notes
- This is an infrastructure library; the rules above are framework/behavioral contract rules, not SAIF insurance-domain business rules. No domain-specific rule was found in the compiled scope.

> **Note:** Statements marked with `*` were inferred from code structure, naming conventions, or
> contextual evidence where direct confirmation was not available in source artifacts.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Security and authorization](security-authorization.md)
- [API reference](api/index.md)
- [SAIF.Web.API.Core overview](../saif-web-api-core/overview.md)

## Sources
- src/SAIF.Web.API.Clients/JsonHttpClient.cs
- src/SAIF.Web.API.Clients/ClientInfo.cs
- src/SAIF.Web.API.Clients/ExceptionHandler.cs
- src/SAIF.Web.API.Clients/JsonSerializer.cs
- src/SAIF.Web.API.Clients/Extensions/HttpContextExtensions.cs (excluded from this project)
- src/SAIF.Web.API.Core/SAIFException.cs
