# Response Processing — Portal Policy Template Bundles

This folder contains **response-processing** policy template bundles for the Layer7 API Developer Portal — templates that run **last** in any Policy Template Category chain, after authentication, validation, and routing have completed, so they can finalise the request/response state before the global Portal analytics policy reads it. The Portal Templates Guide places this kind of template in the **Response Processing** category, alongside *Capture Metrics* (see the [Layer7 API Developer Portal Policy Templates Guide](../Layer7%20API%20Developer%20Portal%20Policy%20Templates%20Guide.md) for category guidance).

## Background — the Portal analytics variables

The Portal Templates Guide documents a set of `portal.analytics.*` context variables that are read by a **global policy at end-of-message processing** and shipped to the Portal for reporting. To populate that report, the variables must be set during the request/response lifecycle and exported via the **Export Variables from Fragment** assertion.

In a typical Portal policy template chain, those variables are populated piecemeal by the templates that run earliest:

- **Authentication templates** (e.g. [`../Authentication/OAuth2Validation-1.0`](../Authentication)) set `portal.analytics.apikey`, `portal.analytics.application.name`, `portal.analytics.application.uuid`, `portal.analytics.authType`, `portal.analytics.organization.name`, and `portal.analytics.organization.uuid`.
- **Threat Protection** ([`../ThreatProtection/ThreatProtection-1.0`](../ThreatProtection)) sets `portal.analytics.action`, `portal.analytics.api.uuid`, and on rejection `portal.analytics.response.code`.
- **Rate / Quota** templates ([`../RateQuota/`](../RateQuota)) set `portal.analytics.response.code = 503` on rejection.

What's *not* covered by any of those templates is **the happy path**: when no template short-circuited and the request flowed through to the backend and back. The variables `portal.analytics.routingTotalTime` and `portal.analytics.response.code` only become meaningful *after* routing, so they need a final response-processing pass to capture them. That's the job of `CaptureMetrics`.

## Bundles in this folder

### `CaptureMetrics-1.0.0.json`

Installs the **`CaptureMetrics-1.0`** template — the canonical Response Processing template. Fully automatic, no publisher inputs. It runs six steps:

1. **Backfill `portal.analytics.action` (SOAP-aware)** — only if no upstream template set it. Uses `${request.http.header.soapaction}` if a SOAPAction header is present, otherwise `${request.http.uri}`. Matches the SOAP-vs-REST guidance in the Templates Guide §4.
2. **Backfill `portal.analytics.api.uuid`** — only if not already set. Uses `${portal.managed.service.apiId}` (the Portal-managed input).
3. **Backfill `portal.analytics.authType`** — defaults to `NO_AUTH` if no auth template ran. Auth templates set this to `API_KEY`, `OAUTH_2`, etc.
4. **Always capture `portal.analytics.routingTotalTime`** — set from the Gateway built-in `${request.routingTotalTime}` (populated by the **Route via HTTP(S)** assertion that ran earlier in the chain).
5. **Backfill `portal.analytics.response.code`** — only if no upstream template set it explicitly. Uses `${response.http.status}` (the actual backend response status). Threat-protection / auth / rate-quota templates set this explicitly to `4xx` / `503` on rejection; on the happy path the backend status is what matters.
6. **Export every `portal.analytics.*` variable** — via the **Export Variables from Fragment** assertion, so the Portal global analytics policy reads the finalised values.

**Backfill semantics:** every variable is set *only if currently empty*. This means `CaptureMetrics` never overrides a value that an earlier template set deliberately — it only fills in the gaps. Run-after-everything-else is therefore safe even if one of the earlier templates was already careful about analytics.

**Arguments (inputs):** all hidden from the publisher (`guiPrompt: false`). The names match the encass-results that the bundles in [`../Authentication/`](../Authentication), [`../ThreatProtection/`](../ThreatProtection), and [`../RateQuota/`](../RateQuota) already publish, so the parent policy can wire them in directly.

| Name | Source upstream | Used here for |
| --- | --- | --- |
| `portal.managed.service.apiId` | Portal | Backfill of `portal.analytics.api.uuid`. |
| `portal.analytics.action` | upstream auth / threat protection | Read for backfill check; passed through. |
| `portal.analytics.api.uuid` | upstream auth / threat protection | Read for backfill check; passed through. |
| `portal.analytics.apikey` | upstream auth | Read; passed through. |
| `portal.analytics.application.name` | upstream auth | Read; passed through. |
| `portal.analytics.application.uuid` | upstream auth | Read; passed through. |
| `portal.analytics.authType` | upstream auth | Read for backfill check; passed through. |
| `portal.analytics.organization.name` | upstream auth | Read; passed through. |
| `portal.analytics.organization.uuid` | upstream auth | Read; passed through. |
| `portal.analytics.response.code` | upstream auth / threat / rate-quota | Read for backfill check; passed through. |

**Exported context variables (outputs):** every `portal.analytics.*` variable, plus the new `portal.analytics.routingTotalTime`:

`portal.analytics.action`, `portal.analytics.api.uuid`, `portal.analytics.apikey`, `portal.analytics.application.name`, `portal.analytics.application.uuid`, `portal.analytics.authType`, `portal.analytics.organization.name`, `portal.analytics.organization.uuid`, `portal.analytics.response.code`, `portal.analytics.routingTotalTime`.

### Note on the `application.uuid` extraction

The Portal Templates Guide flags the `portal.analytics.application.uuid` extraction as a **TODO** ("Provide extraction sample"). The extraction itself is not done in `CaptureMetrics-1.0` because the source data (`apiKeyRecord.xml` ➜ `CustomMetaData` ➜ `_portalApplicationUuid`) is only available immediately after **Portal API Key Look Up (Generic)** runs. The canonical extraction lives in [`../Authentication/OAuth2Validation-1.0.0.json`](../Authentication/OAuth2Validation-1.0.0.json) (look for the `customFieldsMetadata` / `appUuid` / `EvaluateJsonPathExpressionV2` block). `CaptureMetrics` simply reads whatever the upstream auth template populated — and falls back to leaving the value empty when no auth template ran (e.g. NO_AUTH APIs).

## Where this template fits in a Portal policy chain

The intended order in a Portal policy template chain is:

1. **Threat Protection** ([`../ThreatProtection/ThreatProtection-1.0`](../ThreatProtection)) — runs first.
2. **Authentication** ([`../Authentication/*`](../Authentication)) — runs next; sets the auth-related analytics variables.
3. **Per-Organization rate / quota** ([`../RateQuota/OrgLevelRLQ-1.0`](../RateQuota)) — if applicable.
4. **Per-API rate / quota** — Portal's Standard system templates.
5. **Request validation** ([`../RequestValidation/`](../RequestValidation)).
6. **Routing** ([`../Routing/RouteRequest-1.0`](../Routing)) — populates `${request.routingTotalTime}`.
7. **`CaptureMetrics-1.0`** *(this bundle)* — runs **last**. Finalises every analytics variable for the Portal global analytics policy.

Placing `CaptureMetrics` last guarantees that:

- The backend response is available (so `${response.http.status}` is meaningful).
- `${request.routingTotalTime}` has been populated by the routing assertion.
- Every upstream template has had its chance to set its own `portal.analytics.*` variables (so the backfill logic only fires when a value is genuinely missing).

## Customising the bundle

- **SOAP vs REST.** The Step 1 backfill picks the SOAPAction header when present. If your environment uses a different header (e.g. WS-Addressing `wsa:Action`), edit the `${request.http.header.soapaction}` reference inside the Step 1 branch.
- **Adding fields.** To capture additional metadata (e.g. an `X-Agent-ID` header for AI-agent traceability), add a new `<L7p:SetVariable>` after Step 5 setting `portal.analytics.agent.id` (or whatever variable name your global analytics policy reads), then add the same name to the `<L7p:ExportVariables>` list in Step 6. Don't forget to add it to `encassResults` in the bundle JSON so the Portal advertises it as an output.
- **Suppressing a backfill.** To force `CaptureMetrics` to *only* export upstream-set values without backfilling anything (i.e. require auth/threat templates to set everything explicitly), simply delete the corresponding step. The `<L7p:ExportVariables>` block in Step 6 always exports every variable regardless.

## Conventions

Bundles in this folder follow the same conventions as the other Portal Templates in this repository:

- Distributed as a `*-<version>.json` policy bundle (schema `11.2.1`, `defaultAction: NEW_OR_UPDATE`).
- Installs a policy fragment under `/Portal Templates/` and an encapsulated assertion in the Policy Manager assertion palette (`customAssertions` palette folder).
- Designed to be composed with the bundles under [`../ThreatProtection/`](../ThreatProtection), [`../Authentication/`](../Authentication), [`../RateQuota/`](../RateQuota), [`../RequestValidation/`](../RequestValidation), and [`../Routing/`](../Routing) so that response processing runs last and finalises the analytics variables those upstream templates set.

Contributions are welcome — drop a new `*-<version>.json` bundle into this folder and add a corresponding entry in the **Bundles in this folder** section above.
