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

# Authority and scope

> Read and write on behalf of a person, sign that claim, understand which rows and fields they may see, and handle every identity refusal.

Your API key proves which organization is calling. It does not say which person inside that organization the request is for. Records reads and writes are almost always for a person: someone looking at the records they own, a manager looking at a team. So a trusted server, such as your application's backend, names that person on each request. Nexio then resolves that person's authority from your own systems before it builds any query, and every response states the scope it was served at.

On Records reads and writes, a header can only narrow what your key already reaches. What the person may reach comes from your own systems: your identity provider, your HR directory and your system of record's security groups and business-unit access, and the access plane applies your rules to every read under the `lit` [posture](#posture) (see [Access plane](/data-services/access)).

## Acting-principal headers

| Header                      | Meaning                                                                                                                                                                           |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Nexio-Acting-Principal`  | The person the request is for, as your identity provider's stable user id. Never an email (see [Acting for a person](/authentication#acting-for-a-person)).                       |
| `X-Nexio-Acting-Email`      | That person's verified sign-in email, sent beside the principal. Under the `shadow` and `lit` postures the seat is derived from it, and Nexio records it as the person's address. |
| `X-Nexio-Records-Assertion` | A signature over the identity headers, so Nexio can verify your server sent them. Required once Nexio enables assertion verification for your organization.                       |
| `X-Nexio-Records-Lens`      | `principal:<id>`: a read-only view of another person's data, for a caller whose own scope is `All` or `Platform`.                                                                 |

These headers are asserted by a server you trust, never by a browser. The API sends no CORS headers, so it can only be called server to server.

## The signed assertion

The assertion proves that the identity headers came from your server and were not changed on the way.

* **Format:** `v1.<unix timestamp in seconds>.<hex HMAC-SHA256>`.
* **Signed bytes:** `acting_principal|acting_email|reserved|timestamp`, joined with `|`. Trim each value. Lowercase the email. The third field is reserved: send an empty string. Use an empty string for a header you do not send. The timestamp is the same decimal string that appears in the header.
* **Key:** a signing secret Nexio provisions for the server that asserts identity. It is not your API key.
* **Window:** the timestamp must be within 5 minutes of Nexio's clock, in either direction. Outside it the assertion is stale.

Nexio enables assertion verification for your organization when it provisions the signing secret. A missing or bad assertion is then refused with 403 `assertion_invalid`, and one outside the window with 403 `assertion_stale`.

<CodeGroup>
  ```bash curl theme={null}
  PRINCIPAL="user_01J8Z3K4M5N6P7Q8R9S0T1U2V3"
  EMAIL="dana.ortiz@harborgroup.example"
  TS=$(date +%s)
  SIG=$(printf '%s|%s|%s|%s' "$PRINCIPAL" "$EMAIL" "" "$TS" \
    | openssl dgst -sha256 -hmac "$NEXIO_ASSERTION_SECRET" -hex | sed 's/^.* //')

  curl https://api.usenexio.com/api/v1/records/status \
    -H "Authorization: Bearer $NEXIO_API_KEY" \
    -H "X-Nexio-Acting-Principal: $PRINCIPAL" \
    -H "X-Nexio-Acting-Email: $EMAIL" \
    -H "X-Nexio-Records-Assertion: v1.$TS.$SIG"
  ```

  ```python Python theme={null}
  import hashlib
  import hmac
  import os
  import time

  import requests


  def records_assertion(secret: str, principal: str, email: str) -> str:
      ts = str(int(time.time()))
      signed = "|".join([principal.strip(), email.strip().lower(), "", ts])
      digest = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
      return f"v1.{ts}.{digest}"


  principal = "user_01J8Z3K4M5N6P7Q8R9S0T1U2V3"
  email = "dana.ortiz@harborgroup.example"
  response = requests.get(
      "https://api.usenexio.com/api/v1/records/status",
      headers={
          "Authorization": "Bearer " + os.environ["NEXIO_API_KEY"],
          "X-Nexio-Acting-Principal": principal,
          "X-Nexio-Acting-Email": email,
          "X-Nexio-Records-Assertion": records_assertion(os.environ["NEXIO_ASSERTION_SECRET"], principal, email),
      },
      timeout=30,
  )
  response.raise_for_status()
  print(response.json()["scope"])
  ```

  ```typescript TypeScript theme={null}
  import { createHmac } from "node:crypto";

  function recordsAssertion(secret: string, principal: string, email: string): string {
    const ts = Math.floor(Date.now() / 1000).toString();
    const signed = [principal.trim(), email.trim().toLowerCase(), "", ts].join("|");
    const digest = createHmac("sha256", secret).update(signed).digest("hex");
    return `v1.${ts}.${digest}`;
  }

  const principal = "user_01J8Z3K4M5N6P7Q8R9S0T1U2V3";
  const email = "dana.ortiz@harborgroup.example";
  const response = await fetch("https://api.usenexio.com/api/v1/records/status", {
    headers: {
      Authorization: `Bearer ${process.env.NEXIO_API_KEY}`,
      "X-Nexio-Acting-Principal": principal,
      "X-Nexio-Acting-Email": email,
      "X-Nexio-Records-Assertion": recordsAssertion(process.env.NEXIO_ASSERTION_SECRET!, principal, email),
    },
  });
  if (!response.ok) throw new Error(`status read failed: ${response.status}`);
  console.log((await response.json()).scope);
  ```
</CodeGroup>

The full status response is on [Completeness and errors](/data-services/completeness#preflight-with-the-status-read). Its `scope` block for this person reads:

```json theme={null}
{
  "kind": "Self",
  "principal": "user_01J8Z3K4M5N6P7Q8R9S0T1U2V3",
  "producer_code_count": 1,
  "selection": "boundary",
  "selection_source": "none",
  "home_market": "West Region",
  "home_office": "DEN",
  "home_office_label": "Denver",
  "home_status": "ok"
}
```

## Posture

Each organization has an access-plane posture. It decides where a person's authority comes from.

| Posture  | Authority served                                                                                                                    |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `dark`   | The organization's identity mapping: each person mapped to their employee records and codes in the system of record.                |
| `shadow` | The identity mapping, while Nexio also resolves the access-plane entitlement and records the comparison. Use it to check a rollout. |
| `lit`    | The access-plane entitlement.                                                                                                       |

Send the acting principal on every Records request. Under `shadow` and `lit`, a request without it is refused with 403 `identity_unmapped`.

A posture can be limited to named people. Your organization sets its posture with its own live API key or through a person it has delegated as an access administrator, and Nexio sets it only when you ask. Each change is written to the audit log. How the entitlement is derived from your own systems is on [Access plane](/data-services/access).

## Scope kinds

The resolved scope decides which rows exist for this person. It appears as `scope.kind` in [the scope block](#the-scope-block).

| Kind       | Rows served                                                                                                                            |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `Self`     | The records the person may open, as the system of record's own access records (its business units and record restrictions) bound them. |
| `All`      | The organization's whole connected data set.                                                                                           |
| `Platform` | The whole connected data set, the same rows as `All`.                                                                                  |
| `Office`   | Modeled but not served. Every read for a person with this scope answers 403 `scope_unavailable`.                                       |

## Selection

A selection narrows rows inside the scope. It never widens it. Nexio applies no default selection: a person-scoped caller that sends none is served everything they may open, and the response says so.

| Parameter                                         | Effect                                                                 |
| ------------------------------------------------- | ---------------------------------------------------------------------- |
| `producer=<me or code>`                           | Rows whose primary owner is the person (`me`) or one named owner code. |
| `client_manager=<me or code>`                     | The same, on the record's servicing owner.                             |
| `mine=true`                                       | Narrow the scope to the person's own codes.                            |
| `book=<code>`                                     | Narrow the scope to one code the person may already see.               |
| `account=<key>` (also `client_key`, `account_id`) | One top-level record bounds the read.                                  |

`me` resolves to the person's own verified codes. A person with none, asking for `me`, answers 403 `scope_unavailable`, never an empty list. A well-formed `producer` or `client_manager` code that matches none of the person's rows answers either 200 with no rows or 403 `scope_unavailable`, depending on how the person's access is bounded. A `book` code outside the person's scope answers 403 `scope_unavailable`. A malformed value answers 400 `invalid_request`.

`mine` and `book` ride outside the page cursor: resend them on every page, exactly as you resend the acting-principal headers. Whether `producer` and `client_manager` are fixed by the cursor or resent depends on the register; see [What a cursor fixes](/data-services/registers#what-a-cursor-fixes-and-what-you-resend).

## The scope block

Every JSON response from a read under `/api/v1/records` ends with a `scope` block, except `GET /records/actions`, the `/records/analyses` routes and document content. `GET /records/status` carries its own, fuller `scope` block. The full list, beside the `serving` block, is [Which responses carry each block](/data-services/completeness#which-responses-carry-each-block). [Graph](/data-services/graph) reads carry no `scope` block either. The block looks like this:

```json theme={null}
"scope": { "kind": "Self", "selection": "own", "selection_source": "request" }
```

| Field              | Values                                                                                                                                                                                                                                                                                                                                                                                                   |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kind`             | `Self`, `All`, `Platform`.                                                                                                                                                                                                                                                                                                                                                                               |
| `selection`        | `own` (you asked for `me` or `mine`, or row-level security narrowed a numeric read to the person's own codes), `client_manager` (`client_manager=me`), `code` (a named code or `book=`), `boundary` (a person-scoped read with no selection: everything they may open), `whole` (an `All` or `Platform` read with no selection), `account` (one account key), `office` (an office-bounded numeric read). |
| `selection_source` | `request` (you sent it), `rls` (the system of record's row-level security narrowed a numeric read), `none` (no selection).                                                                                                                                                                                                                                                                               |

Use `selection` to label what you show. A `boundary` read is not "my accounts"; it is every account this person may open.

## Field classes

Some fields belong to a field class, and a role policy can deny a class. On a Records read that returns the field among others, a denied field is blanked: `null` on a nullable field, and the type's empty value (`0`, `""` or `false`) on the few fields that are not nullable. It stays in the shape, so your parser does not change. A run read is different: with an acting principal, a denied compensation key is removed from the run's `output` (see [Runs](/engines/runs#what-each-status-carries)). A route whose whole answer is one denied class refuses instead, with 403 `field_denied`, because an all-null answer would read as a real zero.

The field classes are fixed in code and are the same for every organization. Which classes a person may see follows from their security groups in your system of record.

## Lens

`X-Nexio-Records-Lens: principal:<id>` shows another person's data to a caller whose own scope is `All` or `Platform`. The response carries `serving.lens` with the target, their display name and the scope kind served. From any narrower scope the header is ignored. A lensed write answers 403 `book_lens_read_only`. A lens needs an acting principal: without one it answers 403 `lens_caller_unattributed`.

## Refusal codes

Every refusal is distinct and none falls back to a wider scope.

| Status | Code                                                                      | Cause                                                                                                                                                                                                                                                 | Fix                                                                                                                                                                                                                                                                                                                                                                |
| ------ | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400    | `lens_target_unknown`                                                     | The lens target is malformed or not a person in this organization.                                                                                                                                                                                    | Send `principal:<id>` for a known person.                                                                                                                                                                                                                                                                                                                          |
| 403    | `identity_unmapped`                                                       | No acting principal, or no identity mapping for this person.                                                                                                                                                                                          | Send the principal. If it is sent, the person is not yet matched in your records (see [Access plane](/data-services/access)).                                                                                                                                                                                                                                      |
| 403    | `identity_needs_review`                                                   | The person's mapping is below the confidence bar and waits for a human to confirm it.                                                                                                                                                                 | Ask Nexio to confirm the match.                                                                                                                                                                                                                                                                                                                                    |
| 403    | `identity_suspended`                                                      | The mapping is suspended.                                                                                                                                                                                                                             | Ask Nexio to reinstate it.                                                                                                                                                                                                                                                                                                                                         |
| 403    | `identity_stale`                                                          | The mapping was verified against data that has since changed.                                                                                                                                                                                         | Ask Nexio to re-verify it.                                                                                                                                                                                                                                                                                                                                         |
| 403    | `scope_unavailable`                                                       | The person has no owner code or employee record of their own in the source, the code asked for is outside their scope, `mine` or `book` was sent on a whole-organization scope, their account access could not be resolved, or the scope is `Office`. | Read the message; change the selection, or change the person's access in your system of record.                                                                                                                                                                                                                                                                    |
| 403    | `surface_denied`, `dataset_denied`, `action_denied`                       | The person's policy does not grant this route. The message names what the route needs.                                                                                                                                                                | Change the person's security groups in your system of record. For `action_denied`, an exception for that one workflow action also works; `surface_denied` and `dataset_denied` need an added grant. Your organization sets either with its own live key or through its delegated access administrator (see [Access plane](/data-services/access#changing-access)). |
| 403    | `execution_confirm_required`, `approval_required`, `execution_prohibited` | The action's execution policy needs a confirmation or an approval that the request did not carry, or prohibits the action.                                                                                                                            | For a confirmation, send `X-Nexio-Confirmed` equal to the hex SHA-256 of the request body. Approvals are not part of the public API.                                                                                                                                                                                                                               |
| 403    | `assertion_invalid`, `assertion_stale`                                    | Assertion verification is enabled, and the assertion is missing, wrong or outside 5 minutes.                                                                                                                                                          | Sign as shown above; check your clock.                                                                                                                                                                                                                                                                                                                             |
| 403    | `lens_caller_unattributed`                                                | A lens without an acting principal.                                                                                                                                                                                                                   | Send the principal.                                                                                                                                                                                                                                                                                                                                                |
| 403    | `book_lens_read_only`                                                     | A write through a lens.                                                                                                                                                                                                                               | Write as the person.                                                                                                                                                                                                                                                                                                                                               |

When a qualifying connection exists, `GET /api/v1/records/status` answers five of these refusals as data (200, with `scope.refused` set) so an application can explain them before the first read: `identity_unmapped`, `identity_needs_review`, `identity_suspended`, `identity_stale` and `scope_unavailable`. With no qualifying connection, it admits the caller as other governed routes do, and an identity refusal answers with its HTTP status. The other refusals in this table, including the assertion and lens refusals, answer with their HTTP status on `/records/status` as on every other route.

## Next

<CardGroup cols={2}>
  <Card title="Registers and pagination" href="/data-services/registers">Page through a scoped list.</Card>
  <Card title="Access plane" href="/data-services/access">How each person's access comes from your own systems.</Card>
</CardGroup>
