---
title: Execute JSON API request and return typed envelope
summary: Issue a JSON HTTP request to a SAIF Web API and return the full response envelope, or throw a typed exception.
status: draft
owner: bendy
last_reviewed: 2026-09-15
library: SAIF.Web.API.Clients.35
reference_type: package
operation_id: OP-01
trigger: Caller invokes GetResponse<T>, PostJson<T>, PutJson<T>, PatchJson<T>, or DeleteJson<T>
---

# Execute JSON API request and return typed envelope

Given a base URI, caller identity, and an authenticator, this operation constructs, executes, retries, and validates an HTTP request to a SAIF Web API, returning a typed `ApiResponse<T>` envelope or throwing a typed `SAIFException` subtype.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-01 |
| **Trigger** | Caller invokes `GetResponse<T>`, `PostJson<T>`, `PutJson<T>`, `PatchJson<T>`, or `DeleteJson<T>` with a relative URI segment and (for write verbs) a request body object. |
| **Inputs** | `methodUriSegment` (string), `requestBody` (object, write verbs only); instance state `_serviceBaseUri`, `_clientInfo`, `_authenticator`, `TimeoutOverride`, `SetOnBehalfOfUserHeaders`, `RetryOnStatusCodes`, `RetryDelayInMs`, `EnsureSuccessfulResponse`, `DateTimeZoneHandling`. |
| **Outputs** | `ApiResponse<T>` (`Results`, `Server`, `StatusCode`, `Version`) — or a thrown `SAIFException` subtype. |
| **Primary components** | `JsonHttpClient.GetJsonResponse` (private), `JsonHttpClient.GetApiResponse<T>` |
| **Entry point** | src/SAIF.Web.API.Clients/JsonHttpClient.cs |

## Steps

1. Create the transport client: `new RestClient(_serviceBaseUri)`; assign `Authenticator`; `ClearHandlers()`; `AddHandler("application/json", () => new JsonDeserializer())` ([BR-02](../business-rules.md#br-02-default-ntlm-authentication)).
2. Apply timeout: if `TimeoutOverride > 0`, set `client.Timeout` ([BR-03](../business-rules.md#br-03-conditional-timeout-override)).
3. Build the request: `new RestRequest(methodUriSegment, method)`; assign `request.JsonSerializer = new SAIF.Web.API.Clients.JsonSerializer(DateTimeZoneHandling)` ([BR-07](../business-rules.md#br-07-configured-datetimezonehandling-applied-to-serialization)).
   - This step delegates request-body serialization to a locally-implemented operation.
   - Configures a Newtonsoft-based serializer with `MissingMemberHandling.Ignore`, `NullValueHandling.Include`, `DefaultValueHandling.Include`, and the configured `DateTimeZoneHandling` ([BR-21](../business-rules.md#br-21-serialization-member-and-value-handling)).

   > **Origin**: Step 3.1 is implemented in [Serialize request body](op-04-serialize-request-body.md) (`SAIF.Web.API.Clients.35`) and executed on behalf of this operation.
4. Attach body: if `body != null`, `request.AddJsonBody(body)`, which invokes `JsonSerializer.Serialize` (see [Serialize request body](op-04-serialize-request-body.md)).
5. Add identity headers: `Client-Name` ← `_clientInfo.MachineName`; `Application-ID` ← `_clientInfo.ApplicationId`; `Client-User` ← `_clientInfo.UserId` ([BR-01](../business-rules.md#br-01-identity-headers-on-every-request)).
6. Add impersonation header (conditional): if `SetOnBehalfOfUserHeaders`, add `OnBehalfOfUser` ← `HttpContext.Current.DomainlessUser()` ([BR-04](../business-rules.md#br-04-on-behalf-of-header-forwarding)).
   - In this .35 project, the supporting `HttpContextExtensions.DomainlessUser` source file is not compiled and `System.Web` is not referenced, so this branch has no working implementation here.*
7. Execute: `client.Execute(request)`.
8. Retry decision: if `retries > 0` and `RetryOnStatusCodes` contains the returned status, `Thread.Sleep(RetryDelayInMs)` then recurse with `retries - 1` ([BR-05](../business-rules.md#br-05-transient-failure-retry)).
9. Error gate: if `EnsureSuccessfulResponse`, call `ExceptionHandler.EnsureSuccessfulResponse(response)` ([BR-06](../business-rules.md#br-06-unsuccessful-response-raises-typed-exception)).
   - This step delegates to a locally-implemented operation.
   - Evaluates whether the status is outside 200–399 and, if so, maps the response to a typed exception.

   > **Origin**: Step 9.1 is implemented in [Translate HTTP failure to typed exception](op-03-translate-http-failure.md) (`SAIF.Web.API.Clients.35`) and executed on behalf of this operation.
   - On failure: an exception is thrown and propagates to the caller; steps 10–12 are skipped.
10. Wrap raw response: build `JsonResponse { Content, Headers, StatusCode, SuccessfulRequest = (int)StatusCode < 400 }`.
11. Deserialize payload: `GetApiResponse<T>` → `GetContentArray<T>(content, DateTimeZoneHandling)` into `Results` ([BR-08](../business-rules.md#br-08-array-deserialization-normalization), [BR-09](../business-rules.md#br-09-string-deserialization-failure-fallback)).
12. Populate envelope metadata: `Server` ← first header named `Server-Name`; `StatusCode` ← `jsonResponse.StatusCode`; `Version` ← `jsonResponse.Version` ([BR-10](../business-rules.md#br-10-server-name-from-response-header)).

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
	Start([Verb method invoked]) --> S1["Create RestClient and<br>RestRequest"]
	S1 --> S2["Add identity and<br>impersonation headers"]
	S2 --> S3[Execute request]
	S3 --> D{"Retryable status and<br>retries remain?"}
	D -->|Yes| Retry["Sleep RetryDelayInMs,<br>retry"]
	Retry --> S3
	D -->|No| Gate{"EnsureSuccessfulResponse<br>and status outside<br>200-399?"}
	Gate -->|Yes| EndErr(["Throw typed<br>SAIFException"])
	Gate -->|No| Deserialize["Deserialize payload into<br>ApiResponse"]
	Deserialize --> EndOk([Return ApiResponse T])
```

## Failure and edge-case behavior
- An exception thrown at step 9 propagates to the caller; steps 10–12 are skipped.
- Retry at step 8 happens before the error gate, so retryable failures (502/503/401/transport-failure `0`) are re-attempted up to 3 times before any exception is raised.
- `ApiResponse<T>.Version` is never populated by this library: `GetApiResponse<T>` copies `jsonResponse.Version`, but `GetJsonResponse` never assigns `JsonResponse.Version` when constructing the object, so the value is always null.*

> **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
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

## Sources
- src/SAIF.Web.API.Clients/JsonHttpClient.cs
- src/SAIF.Web.API.Clients/JsonResponse.cs
