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

# Asking for Fields

> Get only the customer details you need at each step, and turn away credentials that share more.

Ask for only what the current step needs. A sign-in step might request a hashed email, while checkout might request a name and address.

Your published KYA profile defines those fields, and the verifier refuses a credential that shares anything outside the selected scope.

This page covers two steps of the [site setup](https://docs.baselayer.com/docs/kya-accepting-agents.md#set-it-up): publishing your profile, and answering protected pages with `401`. It also shows how to pass the scope to your token route.

## How a scope moves through the flow

1. Your site publishes a named scope in `kya-profile.json`.
2. A protected page returns that scope in `KYA-Disclosure-Scope`.
3. The agent gets a credential minted for the scope and sends the same reference to your token route.
4. Your token route resolves the scope from the published profile and passes it to `verify`.

## Publish your profile

Every site that accepts KYA credentials publishes this file. Host it at `/.well-known/kya-profile.json` on your domain. Each entry in `scopes` is one step and lists the exact fields it may read.

```json theme={null}
{
  "profile_id": "shop-example",
  "domain": "shop.example",
  "publisher": { "name": "Example Shop" },
  "class_attestation": {},
  "scopes": [
    {
      "name": "returning-customer",
      "tier": "standard",
      "purpose": "Find the customer's existing account",
      "legal_basis": "contract",
      "fields": ["hashes.email"],
      "retention_days": 30
    }
  ]
}
```

Every field is required. The SDK refuses a profile with a missing or wrongly typed field. The [Profile field reference](#profile-field-reference) below describes each one.

### Fields a scope can list

These are the details a credential can carry. The technical name is what a KYA profile and `disclosed_fields` use.

> **View all person fields**
>
> | Detail | Technical name |
> | - | - |
> | First name | `given_name` |
> | Middle name | `middle_name` |
> | Last name | `family_name` |
> | Full name | `name` |
> | Email address | `email` |
> | Phone number | `phone_number` |
> | Street address | `address.street_address` |
> | City | `address.locality` |
> | State or region | `address.region` |
> | ZIP or postal code | `address.postal_code` |
> | Country | `address.country` |
> | Whether the address was validated by USPS | `address.usps_validated` |
> | Date of birth | `birthdate` |
> | Birth year | `birth_year` |
> | Last four digits of the SSN | `ssn_last_4` |
> | Country of residence | `residency.country` |
> | State or region of residence | `residency.region` |
> | Matching hash of the name | `hashes.name` |
> | Matching hash of the email | `hashes.email` |
> | Matching hash of the phone number | `hashes.phone_number` |
> | Matching hash of the address | `hashes.address` |
> | Matching hash of the SSN | `hashes.ssn` |

> **View all business fields**
>
> | Detail | Technical name |
> | - | - |
> | Legal name | `name` |
> | Trade names (DBAs) | `dbas` |
> | EIN (federal tax ID) | `ein` |
> | Business structure, such as `llc` | `structure` |
> | State of incorporation | `state_of_incorporation` |
> | Country of incorporation | `country_of_incorporation` |
> | Date of incorporation | `date_of_incorporation` |
> | Street address | `address.street_address` |
> | City | `address.locality` |
> | State or region | `address.region` |
> | ZIP or postal code | `address.postal_code` |
> | Country | `address.country` |
> | Address type: registered, mailing, physical or residential | `address.address_type` |
> | Email address | `email` |
> | Website | `website` |
> | Website domain, such as `acme.co.uk` | `domain` |
> | Registration status: active, inactive or dissolved | `registration_status` |
> | Matching hash of the legal name | `hashes.name` |
> | Matching hash of the EIN | `hashes.ein` |
> | Matching hash of the email | `hashes.email` |
> | Matching hash of the address | `hashes.address` |

When a platform mints a business credential, the business details go in `disclosed_fields` and the acting person's details go in `actor_disclosed_fields`, using the person field names.

### Readable values or hashes

A credential can share a detail in one of two ways. The platform sets the mode when it mints:

* `CLEARTEXT_AND_HASH` (default) shares the requested paths. These can be readable values, like `email` or `address.postal_code`, and recognition hashes, like `hashes.email`.
* `HASH_ONLY` shares only recognition hashes. Each requested field is swapped for its hash:

  * `email` → `hashes.email`
  * `given_name` → `hashes.name`
  * `address.*` → `hashes.address`

  Left out: `address.usps_validated`, `address.address_type`, and details with no hash (`birthdate`, `birth_year`, residency).

A recognition hash is `sha256:` followed by a hex digest of a normalized value. A site can compute the same hash from its own records and match a customer without ever receiving the value.

## Ask for a scope

When an agent with no token calls a protected page, respond `401` with a `KYA-Disclosure-Scope` header naming the scope:

```http theme={null}
HTTP/1.1 401 Unauthorized
KYA-Disclosure-Scope: shop-example#returning-customer
```

The reference is `<profile_id>#<scope_name>`. The agent reads your profile, gets a credential minted for that scope, and sends the same reference as `scope` with its credential.

## Enforce the scope

Load your profile, look up the scope the agent sent, and pass it to `verify`. If the reference doesn't match a scope in your profile, refuse with `UNKNOWN_DISCLOSURE_SCOPE`.

> [!NOTE]
>
> The KYA SDK is coming soon. The packages aren't published yet, so the code on the rest of this page won't run until they are.

```ts TypeScript theme={null}
import { DisclosureProfile, VerificationFailure } from "@baselayer/kya";

// The same JSON you publish at /.well-known/kya-profile.json.
const profile = DisclosureProfile.parse(JSON.parse(profileJson));

// POST /oauth/token. scopeReference is the scope field of the form body.
async function scopedTokenEndpoint(subjectToken: string, scopeReference: string) {
  const scope = profile.resolveReference(scopeReference);
  if (scope === null) return oauthError(VerificationFailure.UnknownDisclosureScope);

  const report = await verifier.verify(subjectToken, { scope });
  if (!report.accepted) return oauthError(report.failure);

  // The credential shares only this scope's fields.
  return tokenResponse(report.exchangeClaims());
}
```

```python Python theme={null}
from baselayer_kya import DisclosureProfile, VerificationFailure

# The same JSON you publish at /.well-known/kya-profile.json.
profile = DisclosureProfile.model_validate_json(profile_json)


# POST /oauth/token. scope_reference is the scope field of the form body.
async def scoped_token_endpoint(subject_token: str, scope_reference: str):
    scope = profile.resolve_reference(scope_reference)
    if scope is None:
        return oauth_error(VerificationFailure.UnknownDisclosureScope)

    report = await verifier.verify(subject_token, scope=scope)
    if report.failure is not None:
        return oauth_error(report.failure)

    # The credential shares only this scope's fields.
    return await token_response(report.exchange_claims())
```

`oauthError` and `tokenResponse` are the helpers from the [SDK quickstart](https://docs.baselayer.com/docs/kya-sdk-quickstart.md#the-two-helpers).

An unknown scope gets `400` with `invalid_scope`.

### What the verifier enforces

* A credential is refused with `SCOPE_VIOLATION` if it shares any field outside the selected scope's `fields`.
* The whole credential is refused. The SDK never keeps the allowed fields and drops the rest, so you can only read what your published step says you read.
* A field listed by the scope may still be absent if Baselayer doesn't have a value for it.
* Check required fields after verification, before you make your site's decision.

> [!WARNING]
>
> If you call `verify` without a scope, the SDK does not check the shared fields against any list.

## Read the fields

`report.credential.disclosed` holds the shared fields as `{ path: value }`.

To show a field to a person, `claimLabel(vct, path, "en-US")` turns a path into a readable label, such as "Email address (hashed)" for `hashes.email`. It returns `null` when there is no label.

```ts TypeScript theme={null}
import { claimLabel } from "@baselayer/kya";

if (report.accepted) {
  const { vct, disclosed } = report.credential;
  for (const [path, value] of Object.entries(disclosed)) {
    console.log(claimLabel(vct, path, "en-US") ?? path, value);
  }
}
```

```python Python theme={null}
from baselayer_kya import claim_label

if report.failure is None:
    vct = report.credential.vct
    for path, value in report.credential.disclosed.items():
        print(claim_label(vct, path, "en-US") or path, value)
```

## Profile field reference

Field paths are dotted paths to single values, such as `given_name`, `hashes.email` or `address.postal_code`. List each value you need.

> [!NOTE]
>
> A scope that lists only `address` does not cover `address.postal_code`.

For every field a credential can share, in plain words, see [Fields a scope can list](#details-a-credential-can-share).

### Profile identity

| Field | Type | What it is |
| - | - | - |
| `profile_id` | string | Your profile's ID. Scope references start with it. |
| `domain` | string | Your domain. |
| `publisher` | object of strings | Who publishes the profile. |
| `class_attestation` | object of strings | Your class attestation, as free-text name and value pairs, for example `{ "class": "merchant" }`. It can be empty. |
| `scopes` | list | One entry per step. |

### Each scope

| Field | Type | What it is |
| - | - | - |
| `name` | string | The step's name. |
| `tier` | string | The step's tier. Free text, for example `standard`. There is no fixed list. |
| `purpose` | string | Why you need the fields. |
| `legal_basis` | string | Your legal basis for reading them. Free text, for example `contract`. There is no fixed list. |
| `fields` | list of strings | The exact field paths this step may read. |
| `retention_days` | whole number | How long you keep what you read. |

For how these fields are carried inside the credential, see [Disclosures](https://docs.baselayer.com/docs/credential-format-reference.md#disclosures) in the Credential Format Reference.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.