# Redaction and stored data

> Which request parameters are masked, which bodies are stored by shape only, and the one thing stored as-is.

Source: https://requizon.boring-observability.dev/docs/redaction
Section: Configuration — Requizon documentation (version 0.2)
Updated: 2026-09-16

---

A recorder for outbound calls sits exactly where your credentials travel: API keys in query strings, passwords in login bodies, tokens in headers. Where Requizon cannot tell whether something is a secret, it leaves it out. This page lists exactly what it keeps, including the one place it stores data as it found it.

## What a row holds

| Column | Stored |
| --- | --- |
| `api_name`, `host` | As described in [Naming APIs](https://requizon.boring-observability.dev/docs/naming-apis) |
| `method`, `status_code`, `duration_ms` | As they were |
| `path` | Normalised, without the query string. See [Paths](https://requizon.boring-observability.dev/docs/paths) |
| `failure_type`, `failure_message` | For failures only. See [Failure detection](https://requizon.boring-observability.dev/docs/failure-detection) |
| `request_params` | Redacted, as below |
| `response_body` | For failures only, **unredacted** |

Request and response headers are never stored, so an `Authorization` or `X-Api-Key` header does not reach the table in any form.

## Request parameters

Where the parameters come from depends on the method:

- **GET and DELETE**: the query string, parsed into named parameters. A body is ignored.
- **Every other method**: the body. A JSON body (a `Content-Type` containing `json`) and a form body (`application/x-www-form-urlencoded`) are parsed into named parameters. Anything else is [unparsed](#unparsed). The query string is not recorded.

### Redaction

Before storage, every parameter's name is lowercased and checked against two lists. A match replaces the value with `***`. Nested arrays are walked, so `credentials.api_secret` is caught as well.

```php
'recording' => [
    // ...

    // Redacted when the name contains one of these: user_password, api_secret_key, auth_token
    'redact_patterns' => ['pass', 'secret', 'token', 'apikey', 'api_key', 'auth'],

    // Redacted on an exact name match, for APIs that abbreviate: ?l=user&p=password
    'redact_exact' => ['p', 'l', 'pwd'],
],
```

```json
{
    "username": "rest@example",
    "password": "***",
    "periodFrom": "12.10.2026",
    "credentials": { "api_secret": "***", "region": "eu" }
}
```

Add the names your own APIs use. When you replace either list in your config, include the defaults you still want: the list you write replaces the default one.

> **Substrings over-redact**
>
> Matching on part of a name is deliberately generous. `pass` also catches `passenger_count` and `auth` catches `author`, and those values are stored as `***`. If a parameter you need to read is being hidden, rename the pattern to something more specific (`password` instead of `pass`) rather than removing it.

### Bodies Requizon cannot parse

Redaction works by parameter name, so a body that cannot be parsed into names is a body whose credentials Requizon cannot find: XML, SOAP, `text/plain`, `multipart/form-data` uploads, and anything with no usable `Content-Type`. By default such a body is recorded by shape only:

```json
{ "_unparsed": "application/xml, 1482 bytes" }
```

If you know your unparsed bodies carry nothing sensitive, store them. They are kept verbatim, cut to `recording.request_body_max_bytes` (4 KB):

```php
'recording' => [
    // ...
    'store_unparsed_bodies' => true,   // stored as {"_raw": "<?xml ..."}
],
```

Two kinds of body are never read at all. A body larger than `recording.max_body_read_bytes` (256 KB) is recorded by shape from its declared size, which keeps a file upload from being loaded into memory on every request. A body that cannot be rewound after reading is skipped, and the row has no parameters.

## Response bodies are stored as they arrived

The response body of a failed call is stored so you can read what the API actually said, and it is not redacted. Parameter names in an arbitrary response are not a reliable signal, and a body with values masked is much less useful when you are debugging it. What that means in practice:

- An API that echoes your request back in its error response stores your credentials in `http_requests`.
- A `401` or `400` from a token endpoint can contain the token request it rejected.
- A failed call to an API that returns personal data stores that personal data.
- A message returned by your [failure detector](https://requizon.boring-observability.dev/docs/failure-detection) is stored as-is too.

These are the settings that bound it:

- **`retention.detail_days`** decides how long those bodies exist. The default is 14 days; a few days is often enough to debug with. The hourly rollup holds no bodies and is unaffected.
- **`recording.response_body_max_bytes`** caps how much of each body is kept (64 KB).
- **`connection`** can put Requizon's tables on a separate database with its own access controls and backup policy.
- **`ignore_hosts`** keeps an API whose traffic must not be stored anywhere out of the table entirely.

> **Treat http_requests as sensitive**
>
> Give it the same care as your application logs: restrict who can query it, think about what your backups copy, and keep the `viewRequizon` gate as narrow as the table deserves. The dashboard shows these bodies to anyone the gate lets in.

## Secrets in URLs

The query string is never part of the stored path, and a path segment longer than 40 characters is stored as `:id`, which covers most signed tokens. A short secret placed directly in the path, such as `/hooks/abc123/send`, is stored as written. Use a [path resolver](https://requizon.boring-observability.dev/docs/paths#resolver) to mask it for that host.


## Common questions

### Can Requizon store API keys or passwords?

Request parameters are redacted by name, so a parameter called api_key or password is stored as ***. Two things can still hold credentials: an XML or plain-text body if you enable store_unparsed_bodies, and the response body of a failed call, which is stored unredacted. Keep detail_days short and treat http_requests as sensitive.
