---
title: "Create or replace a shadow-mode prompt-capture scope (platform admin)"
method: PUT
path: "/v1/admin/inference/shadow-capture"
tags: ["Internal"]
---

# Create or replace a shadow-mode prompt-capture scope (platform admin)

`PUT /v1/admin/inference/shadow-capture`

Upserts the (orgId, modelId) capture scope the inference gateway polls (notes/shadow-capture-design.md): while live, matching request/response payloads are mirrored to Firehose -> S3 for offline replay/dataset building. ttlMinutes is required (max 1440/24h): a capture toggle that outlives its purpose is a privacy incident, so every scope self-expires. A second PUT for the same (orgId, modelId) overwrites the first, which is how an operator extends a scope's TTL.

## Request body

- PutShadowCaptureScopeRequest — Creates or replaces the scope for (orgId, modelId). A second PUT for the same pair overwrites the first (upsert), which is how an operator extends a scope's TTL: PUT again with a fresh ttlMinutes.
  - `allowAllOrgs` boolean — Must be true when orgId is "*"; the second-confirm switch fleet-wide capture requires
  - `captureResponse` boolean — Defaults to false (request + metadata only) when omitted
  - `enabled` boolean, required
  - `modelId` string, required — Public catalog model to scope capture to, or "*" for every model
  - `note` string
  - `orgId` string, required — Org to scope capture to, or "*" for every org (requires allowAllOrgs: true)
  - `sampleRate` number, double — Defaults to 1.0 (capture every matching request) when omitted
  - `ttlMinutes` integer, required — Required. How long the scope stays live from now; the design's mandatory auto-off (max 24h).

## Response `200`

Saved

- ShadowCaptureScope — One shadow-mode prompt-capture toggle for the inference gateway (notes/shadow-capture-design.md): while active, the gateway mirrors matching (orgId, modelId) request/response payloads to Firehose -> S3 for offline replay/dataset building. orgId and/or modelId may be "*" (wildcard); an all-orgs scope additionally requires allowAllOrgs (a second confirm field, so a fat-fingered orgId can never silently become fleet-wide capture).
  - `allowAllOrgs` boolean — Required (true) for a scope whose orgId is "*"; ignored otherwise
  - `captureResponse` boolean, required — Also mirror the response payload (unary: scrubbed body; streaming: usage + timing only, see the gateway's capture-point limitation note). false captures the request + metadata only.
  - `createdAt` string
  - `createdBy` string
  - `enabled` boolean, required
  - `expiresAt` string, required — RFC3339 UTC; the scope self-expires here regardless of the DynamoDB TTL sweep timing. Mandatory: a capture toggle that outlives its purpose is a privacy incident.
  - `modelId` string, required — Public catalog model this scope governs, or "*" for every model
  - `note` string — Free-text operator note, e.g. what dataset this scope is for
  - `orgId` string, required — Org this scope governs, or "*" for every org (requires allowAllOrgs)
  - `sampleRate` number, double, required — Fraction of matching requests captured: 0 captures nothing, 1 captures every match
  - `updatedAt` string

## Other responses

- `400` — The request is invalid
- `401` — Missing or invalid API key
- `403` — API key lacks the required scope
- `503` — A required integration (e.g. payments) is not configured

---

[API](https://skmtc.net/openrelay/apis/openrelay-api.md) · [All operations](https://skmtc.net/openrelay/apis/openrelay-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/openrelay/openrelay-api/revisions/d728989c2568/schema)
