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

# Per-person compliance

> Recreate a previous provider's per-person training compliance export from /v1 assignments, enrollments, and courses.

This report is the training half of a previous provider's per-person
compliance export: directory identity plus one row per person per training
enrollment (module status and dates).

The grain is long-form — person × assignment — because that is what
`GET /v1/assignments/{id}/enrollments` already returns. The query does not
pivot modules into columns.

<Note>
  Policy acknowledgements are not part of this report. Living Security has
  no policy-acceptance API, so those legacy columns are out of scope, not a
  pending mapping. See [Divergences from the legacy export](#divergences-from-the-legacy-export).
</Note>

Setup, credentials, pagination, and rate-limit retry are the same as the
[Power BI / Excel connectivity guide](/developers/v1/powerbi). Add the shared
`Fn_FetchAll` query first; this report calls it by name.

## Required key scopes

| Scope | Used for |
| - | - |
| `training:read` | `GET /v1/assignments` and `GET /v1/assignments/{id}/enrollments` |
| `course:read` | `GET /v1/courses` (catalog titles) |

## Source endpoints

| Endpoint | Role |
| - | - |
| `GET /v1/assignments` | Assignment list: `assignment_id`, title, `course_id`, lifecycle status, `created_at`. The query does not filter by status, so active, paused, and completed assignments all appear (historical compliance). |
| `GET /v1/assignments/{id}/enrollments` | Per-learner progress. One paged walk per assignment. This is the fact grain and the only `/v1` resource in this report that carries email as a reporting join key. |
| `GET /v1/courses` | Resolves `course_id` → course title (the training-module name). `Assignment.title` is a campaign label, not the module name. |

This query does **not** call `GET /v1/entities`. The enrollment row
already carries `email` and `display_name`, and the entity list has no
department or job title — so walking it would not populate directory
columns the enrollment row does not already have.

### Grain

One row per enrollment (person × assignment). A person assigned the same
course through two assignments produces two rows. A person with no
enrollments does not appear — `/v1` cannot emit a usable directory row
without email, and inventing person-only rows would mix grains.

Request budget: one paginated walk per assignment, sequentially, under the
shared per-key rate limit. Always set `limit = "500"`.

## Column mapping

Legacy column names are from your previous provider's export. `/v1` fields
are the public API names; the query aliases some of them for the report.

| Legacy column | `/v1` field or derivation | Report column |
| - | - | - |
| Email | `Enrollment.email` | `email` |
| Full name | `Enrollment.display_name` | `full_name` |
| Department | Not on `/v1` | Omitted, not nulled |
| Other directory (title, manager, location, …) | Not on `/v1` — entities carry email and display name only. | Omitted |
| Training module / course name | `Course.title`, falling back to `Assignment.title` | `course_name` |
| Assignment / module status | `Enrollment.status` — `not_started` \| `in_progress` \| `completed` \| `overdue` \| `withdrawn` | `enrollment_status` |
| Assigned date | `Enrollment.assigned_at` | `assigned_at` |
| Completed date | `Enrollment.completed_at` | `completed_at` |
| Due date | `Enrollment.due_at` (the legacy export may not have had this; included because the API does) | `due_at` |
| Policy acceptance / acknowledgement | No policy domain | Omitted |
| Policy name, accepted date | No policy domain | Omitted |

Join keys kept for a star-schema model: `entity_id`, `assignment_id`,
`course_id`, `user_assignment_id`.

The query also keeps `course_slug`, `assignment_title`, `passed`,
`attempts`, `assignment_lifecycle_status`, and `assignment_created_at`.
`enrollment_status` is the learner's progress, not the assignment lifecycle
(`active` / `paused` / `completed`).

## Divergences from the legacy export

* **Policy acceptances are not available.** This platform has no policy
  domain — there is no `/policies`, `/policy_acceptances`, or equivalent
  `/v1` resource. Do not treat those columns as a pending mapping. They
  are out of scope.
* **Directory extras are omitted, not nulled.** Department, job title,
  manager, and location are not on `/v1`. The query does not invent empty
  columns for them.
* **Due date is included** even if the legacy export did not have it,
  because `Enrollment.due_at` is on the API.
* **Course name prefers the catalog title.** `Course.title` is the module
  name. `Assignment.title` is used only when the catalog row is missing
  (deleted or unpublished).
* **Long-form, not pivoted.** One row per enrollment, not one row per
  person with a column per module.

## Next steps

<CardGroup cols={2}>
  <Card title="Power BI and Excel setup" href="/developers/v1/powerbi">
    Credentials, pagination, and loading these queries in Power BI or Excel.
  </Card>

  <Card title="Email phishing log" href="/developers/v1/reports/email-phishing-log">
    Event-level phishing interactions for the trailing 12 months.
  </Card>

  <Card title="Most susceptible users" href="/developers/v1/reports/most-susceptible-users">
    Per-person 12-month click, credential, and report rollup.
  </Card>

  <Card title="API keys" href="/developers/developer-hub/api-keys">
    Grant `training:read` and `course:read` on the key that refreshes this report.
  </Card>
</CardGroup>


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