> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mf-atlas.space/llms.txt
> Use this file to discover all available pages before exploring further.

# PII Masking

> How personally identifiable information is masked on every read, and how to reveal a single field with an audit trail.

Personally identifiable information (PII) is encrypted at rest and **masked on
every read**. Plaintext is available only through a per-field unmask endpoint
that writes an audit record for every access.

## What is masked, and how

`GET /api/investors/v1/:id` (and every other investor read) returns these
fields masked:

| Field               | Example input            | Masked output     | Rule                                                         |
| ------------------- | ------------------------ | ----------------- | ------------------------------------------------------------ |
| PAN                 | `ABCDE1234F`             | `AB******4F`      | First 2 + last 2 visible.                                    |
| Bank account number | `311242065229`           | `********5229`    | Last 4 visible.                                              |
| Email               | `john.doe@example.co.in` | `j***@e***.co.in` | First char of local part + first char of domain label + TLD. |
| Mobile              | `9876543210`             | `******3210`      | Last 4 visible.                                              |

Values of 4 characters or fewer mask to `****` entirely.

Masking is applied to: `primary_holder` (pan, email, mobile), every
`joint_holders[]` entry (pan, email, mobile), `guardian.pan`, every
`nominees[].pan` and `nominees[].guardian.pan`, `contact` (email, mobile), and
every `bank_accounts[].account_no`. KYC-status responses mask the PAN too.

<Note>
  Aadhaar is never collected by this API, so there is no Aadhaar field to mask.
</Note>

## Revealing a single field

```http theme={null}
POST /api/investors/v1/:id/unmask
Authorization: Bearer <token>
Content-Type: application/json
```

```json theme={null}
{
  "field": "contact.email",
  "actor": "downstream-user-9",
  "reason": "customer support call #4821"
}
```

Response:

```json theme={null}
{ "success": true, "data": { "field": "contact.email", "value": "john.doe@example.co.in" } }
```

### Fields

| Field    | Required | Notes                                                                                                                                                                                                        |
| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `field`  | yes      | Dotted path of the **single** field to reveal (see allowed set below).                                                                                                                                       |
| `actor`  | yes      | Identifier of the person or system the unmask is *for*. Since the API caller may be an intermediary auth system, this records the real principal. An empty `actor` is rejected with `400 VALIDATION_FAILED`. |
| `reason` | no       | Free-text justification, stored on the audit record.                                                                                                                                                         |

### Allowed `field` values

```
pan
contact.email
contact.mobile
guardian.pan
joint_holders.<n>.pan
joint_holders.<n>.email
joint_holders.<n>.mobile
nominees.<n>.pan
bank_accounts.<account_id>.account_no
```

`<n>` is a zero-based index; `<account_id>` is a bank account's `bnk_...` ID.
Any other value returns `400 VALIDATION_FAILED` — "unknown or non-unmaskable
field".

<Note>
  Unmasking is intended to require the `pii:read` scope. Request it during
  onboarding if your integration needs plaintext PII.
</Note>

## Audit trail

Every unmask call writes an append-only record. If the audit write fails, the
value is withheld and the call returns `500 INTERNAL` — access is never
granted without a recorded trail.

```http theme={null}
GET /api/investors/v1/:id/pii-access
```

```json theme={null}
{
  "success": true,
  "data": [
    {
      "field": "contact.email",
      "actor": "downstream-user-9",
      "reason": "customer support call #4821",
      "request_id": "req_01J8...",
      "at": "2026-09-06T10:31:00Z"
    }
  ]
}
```

## Platform-side protections

* **Access logs** record only `request_id`, account, route, method and status
  — never request bodies, headers or query strings.
* **Exchange call logs** redact PAN, bank account numbers, uploaded file
  contents and authorization headers before storage.
* Secrets and credentials are never returned by any endpoint, including
  `GET /api/auth/v1/me`.
