---
title: SAIF.Web.API operations and processes
summary: Business and technical operations supported by this library.
status: draft
owner: andmil
last_reviewed: 2026-09-15
library: SAIF.Web.API
reference_type: package
---

# SAIF.Web.API operations and processes

This page is the operation catalog and cross-operation hub for this library. Each operation's detailed, step-by-step logic lives in its own page under `operations/`. Ten operations are implemented across the library's five public types; no database, message queue, or file-system operations exist in any path.

## Operation catalog
| Operation | Intent | Trigger | Inputs | Outputs | Rules |
| --- | --- | --- | --- | --- | --- |
| [Post-process an API response](operations/op-01-post-process-an-api-response.md) | Normalize every outbound Web API response with a correlation ID, Splunk lookup URL, structured error body, log entry, and trace entry. | Every HTTP request flowing through the Web API message-handler pipeline where `ApiResponseHandler` is registered. | `HttpRequestMessage`, `CancellationToken`, request-scoped `ILogService`/`IEnvironmentService` (may be null), ambient `HttpContextBase` | `HttpResponseMessage` (original with `Server-Name` header, or rebuilt with `ErrorResponse` body) | [BR-WEBAPI-001](business-rules.md#br-webapi-001-server-name-header-on-every-response) · [BR-WEBAPI-002](business-rules.md#br-webapi-002-normalize-non-success-responses-into-errorresponse) |
| [Generate a log correlation identifier](operations/op-02-generate-a-log-correlation-identifier.md) | Produce a unique, time-ordered correlation ID for the current response. | Post-process an API response, step 6. | none | `string` | [BR-WEBAPI-005](business-rules.md#br-webapi-005-unique-correlation-identifier-per-response) |
| [Build the Splunk error-lookup URL](operations/op-03-build-the-splunk-error-lookup-url.md) | Compose (or fall back to a default) Splunk deep-link URL for the current `MessageID`. | Post-process an API response, step 7. | `IEnvironmentService` (may be null) | `string` URL | [BR-WEBAPI-006](business-rules.md#br-webapi-006-splunk-lookup-url-always-produced) |
| [Map a Web API error to an ErrorResponse](operations/op-04-map-a-web-api-error-to-an-errorresponse.md) | Translate an `HttpError` into the library's normalized `ErrorResponse` DTO, including the 400-classification rule. | Post-process an API response, step 10. | `HttpStatusCode`, `HttpError` | `SAIF.API.Responses.ErrorResponse` | [BR-WEBAPI-002](business-rules.md#br-webapi-002-normalize-non-success-responses-into-errorresponse) · [BR-WEBAPI-003](business-rules.md#br-webapi-003-client-fault-exception-types-map-to-400-bad-request) |
| [Compose the trace and log message](operations/op-05-compose-the-trace-and-log-message.md) | Build the newline-joined trace/log payload, redacting password-like content when debug logging is on. | Post-process an API response, steps 13, 14, 15. | request/response context, `debugMode` flag | newline-joined `string` | [BR-WEBAPI-007](business-rules.md#br-webapi-007-redact-password-material-from-debug-traces) · [BR-WEBAPI-009](business-rules.md#br-webapi-009-trace-every-request-with-debug-gated-verbosity) |
| [Map a SAIF exception to an HTTP status code](operations/op-06-map-a-saif-exception-to-an-http-status-code.md) | Provide a static, catch-clause-based mapping from `SAIF.API.Exceptions.*` types to `HttpStatusCode`. | Called explicitly by a consumer (no call site inside this library). | `Exception` | `HttpStatusCode` | [BR-WEBAPI-011](business-rules.md#br-webapi-011-saif-exception-types-map-to-fixed-http-status-codes) |
| [Enforce HTTPS on an action](operations/op-07-enforce-https-on-an-action.md) | Redirect non-HTTPS requests to HTTPS, with an app-setting-controlled exemption for local requests. | Web API action-filter pipeline, before action execution, on any decorated controller/action. | `HttpActionContext`, `appSettings["RequireSSLForLocal"]` | none (side effect: HTTP redirect) | [BR-WEBAPI-012](business-rules.md#br-webapi-012-https-required-unless-local-exemption-applies) · [BR-WEBAPI-013](business-rules.md#br-webapi-013-redirect-non-https-requests-to-https) |
| [Authorize a request against SAIF roles](operations/op-08-authorize-a-request-against-saif-roles.md) | Decide whether the current caller holds any of the roles/resources declared on `[SaifAuthorize]`, delegating the decision to `ISecurityApiClient`. | Web API authorization-filter pipeline on any decorated controller/action. | `HttpActionContext`, constructed `Roles` string, request-scoped `ISecurityApiClient`/`IEnvironmentService` | `bool` | [BR-WEBAPI-014](business-rules.md#br-webapi-014-authorization-fails-closed-on-missing-dependencies) · [BR-WEBAPI-015](business-rules.md#br-webapi-015-no-roles-specified-authorizes-unconditionally) · [BR-WEBAPI-016](business-rules.md#br-webapi-016-unknown-user-is-not-authorized) · [BR-WEBAPI-017](business-rules.md#br-webapi-017-role-evaluation-uses-or-semantics) |
| [Handle an unauthorized request](operations/op-09-handle-an-unauthorized-request.md) | Log the denial (when the user is known) and answer the caller with a structured 401. | Invoked by `AuthorizeAttribute` when Authorize a request against SAIF roles returns `false`. | `HttpActionContext` | sets `actionContext.Response` to 401 | [BR-WEBAPI-014](business-rules.md#br-webapi-014-authorization-fails-closed-on-missing-dependencies) · [BR-WEBAPI-018](business-rules.md#br-webapi-018-unauthorized-requests-are-logged-and-answered-with-401) |
| [Read the first value of a request header](operations/op-10-read-the-first-value-of-a-request-header.md) | Return the first value of a request header, or an empty string, without ever throwing. | Compose the trace and log message, step 2, and any consumer call. | `HttpRequestMessage`, header key | `string` | [BR-WEBAPI-019](business-rules.md#br-webapi-019-header-reads-never-throw) |

## Process flow diagram
The following diagram visualizes the primary operational flow for the response post-processing pipeline, the dominant cross-cutting path through this library.

```mermaid
flowchart TD
	Start([Request received]) --> Base["Run downstream pipeline<br>via base.SendAsync"]
	Base --> Resolve["Resolve ILogService /<br>IEnvironmentService"]
	Resolve --> Ids["Assign MessageID and<br>MessageIDUrl"]
	Ids --> IsError{"Non-success HttpError<br>content?"}
	IsError -->|No| Trace["TraceInfo — success path"]
	IsError -->|Yes| Msg["GetExceptionMessage"]
	Msg --> ErrResp["GetErrorResponse"]
	ErrResp --> Force400{"ErrorCode == 400?"}
	Force400 -->|Yes| SetStatus["response.StatusCode =<br>400"]
	Force400 -->|No| Rebuild
	SetStatus --> Rebuild["Rebuild response with<br>ErrorResponse body"]
	Rebuild --> LogErr["Log exception, possibly<br>twice"]
	LogErr --> Trace
	Trace --> Header["Add Server-Name header"]
	Header --> End([Return response])
```

## Failure and edge-case behavior
- A downstream exception that is not surfaced as `HttpError` content passes through [Post-process an API response](operations/op-01-post-process-an-api-response.md) unchanged — no normalization occurs.
- `ILogService`/`IEnvironmentService` unavailability degrades gracefully in [Post-process an API response](operations/op-01-post-process-an-api-response.md), but [Authorize a request against SAIF roles](operations/op-08-authorize-a-request-against-saif-roles.md) and [Handle an unauthorized request](operations/op-09-handle-an-unauthorized-request.md) both fail closed (throw) on the same missing-dependency condition.
- A configured `splunksearch` with a blank `splunk.error.query` yields a Splunk URL with an empty query fragment — see [Build the Splunk error-lookup URL](operations/op-03-build-the-splunk-error-lookup-url.md).
- Debug-mode tracing on a context-less request can raise `NullReferenceException` — see [Compose the trace and log message](operations/op-05-compose-the-trace-and-log-message.md).
- `HttpContext.Current` is web-host only for the HTTPS-enforcement filter, and the redirect raises `ThreadAbortException` by design — see [Enforce HTTPS on an action](operations/op-07-enforce-https-on-an-action.md).
- Denial from [Authorize a request against SAIF roles](operations/op-08-authorize-a-request-against-saif-roles.md) always returns `401 Unauthorized` (not `403 Forbidden`) via [Handle an unauthorized request](operations/op-09-handle-an-unauthorized-request.md), while `ExceptionHandler.ToHttpError` maps SAIF's own `UnauthorizedAccessException` to `403` — the two authorization-adjacent paths disagree on status code.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Business rules](business-rules.md)
- [Security and authorization](security-authorization.md)
- [API reference](api/index.md)

## Sources
- `src/SAIF.Web.API/ApiResponseHandler.cs`
- `src/SAIF.Web.API/ExceptionHandler.cs`
- `src/SAIF.Web.API/HttpRequestExtensions.cs`
- `src/SAIF.Web.API/RequireSSLAttribute.cs`
- `src/SAIF.Web.API/SaifAuthorizeAttribute.cs`
- `src/SAIF.Web.API.Tests/SAIF.Web.API.Tests/ApiResponseHandlerTests.cs`
