Hatz Learn reporting

Use the Hatz Learn reporting API to build portfolio dashboards, inspect learner and course progress, and export completion data for downstream reports.

All examples use this base URL:

https://ai.hatz.ai/v1

Authenticate

Send a Hatz API key in the X-API-Key header:

curl --request GET \
  --url 'https://ai.hatz.ai/v1/academy/reporting/portfolio/summary' \
  --header 'X-API-Key: YOUR_HATZ_API_KEY'

An API key uses the key owner's current roles, permissions, tenant scope, and account-team access. Creating a key does not give that user additional access. Use a dedicated reporting user where possible, store the key securely, and never commit it to source control.

Permissions and scope

Reporting reads require View Academy Team Progress (academy_view_team_progress) for each company returned. CSV exports require both that permission and Export Academy Reports (academy_export_reports) in the same company scope.

The built-in MSP roles with portfolio-wide reporting permissions are:

MSP role View reports Export reports Default scope
Primary Admin Yes Yes Accessible client tenants
Admin Yes Yes Accessible client tenants
Compliance Manager Yes Yes Accessible client tenants
Tenant Manager No No No Hatz Learn reporting scope by default

A built-in Client Admin has both reporting permissions for the assigned company only. Custom roles receive only the permissions configured for that role. Company-level permission does not become MSP-wide permission.

Portfolio routes also require the key owner to be an MSP operator. For least-privilege portfolio access, keep the user's normal MSP role and attach an MSP-scoped permission group containing View Academy Team Progress and Export Academy Reports.

Choose a reporting route

Method and route Use it for
GET /academy/reporting/portfolio One metric row per authorized tenant
GET /academy/reporting/portfolio/summary Aggregate metrics across authorized tenants
GET /academy/reporting/tenants/{tenant_id}/summary Aggregate metrics for one tenant
GET /academy/reporting/tenants/{tenant_id}/learners Learner totals, activity dates, and optional course/status filtering
GET /academy/reporting/tenants/{tenant_id}/learners/{learner_id} One learner's status for each applicable course
GET /academy/reporting/tenants/{tenant_id}/courses Enrollment and completion coverage by course
GET /academy/reporting/tenants/{tenant_id}/courses/{course_id} Learner statuses for one course
GET /academy/reporting/exports/capability Whether the key owner can export any permitted tenant, or a specified tenant_id
POST /academy/reporting/exports Generate a CSV report

Use the API Reference for the complete parameter and response schemas.

Export report types

POST /academy/reporting/exports accepts one of four report types:

report_type Scope Output
portfolio_summary Portfolio only; do not send tenant_id One row per active authorized tenant
learner_summary Portfolio only; do not send tenant_id One row per learner with applicable, enrolled, and completed course totals
course_summary One tenant; tenant_id is required One row per course with enrollment and completion coverage
activity_detail Portfolio or one tenant One row per current eligible learner-course pair with active access, status, progress, and activity dates

The synchronous exporter supports up to 100 tenants and 5,000 rows. A larger request returns 422 Unprocessable Entity; contact Hatz Support for a larger export.

Find who has and has not completed training

Use activity_detail to receive one CSV row per current eligible learner-course pair with active access:

curl --request POST \
  --url 'https://ai.hatz.ai/v1/academy/reporting/exports' \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: YOUR_HATZ_API_KEY' \
  --data '{
    "report_type": "activity_detail"
  }'

Restrict the same report to one company by adding its Hatz tenant UUID:

{
  "report_type": "activity_detail",
  "tenant_id": "00000000-0000-0000-0000-000000000000"
}

The response wraps the CSV in JSON:

{
  "filename": "learn-activity-detail-2026-08-28.csv",
  "csv": "Tenant,Course,Course slug,Learner,Email,Status,...",
  "row_count": 3243
}

Parse the JSON response and save the csv string using filename. Filter the CSV's Status column for completed to find completions and for not_enrolled, not_started, or in_progress to find remaining work.

The export excludes historical course versions, ineligible learner-course pairs, revoked access, and retired records. Use the learner or course detail routes when you need those historical or revoked records. The CSV retains the eligibility, current-version, and access-revocation columns for a stable export schema, but exported rows are always eligible and current, with no access-revocation date.

Understand statuses

Detailed learner and course responses can use these statuses:

  • not_enrolled: the learner is eligible for the course but has no enrollment.
  • not_started: the learner is enrolled but has not begun.
  • in_progress: the learner has activity but has not completed the course.
  • completed: the stable course identity is complete. Completion on an earlier published version remains complete.
  • access_revoked: an enrollment exists, but access was revoked.
  • retired: the record belongs to a retired course version.

Use is_eligible_pair and is_current_course when deciding whether historical rows belong in a current-compliance report.

Understand coverage metrics

Every coverage metric contains a numerator, denominator, percentage, and gap:

  • Learner reach: eligible learners with at least one enrollment divided by eligible learners.
  • Enrollment coverage: enrolled learner-course pairs divided by currently eligible learner-course pairs.
  • Completion coverage: completed learner-course pairs divided by currently eligible learner-course pairs.
  • Gap: denominator minus numerator.

percentage is null when the denominator is zero. A legitimate tenant with no eligible learners or courses can therefore show 0/0; this alone does not prove a permission problem.

Pagination and filters

List routes use opaque cursor pagination:

  1. Send limit from 1 to 100. The default is 50.
  2. Read next_cursor from the response.
  3. Send that cursor unchanged with the same filters and sort_direction.
  4. Stop when next_cursor is null.

Learner lists support name or email search, course_id, and status. Course lists support title search. Course detail supports status. Sorting is case-insensitive and accepts asc or desc.

Troubleshooting

401 Unauthorized

The API key is missing, invalid, expired, or revoked. Confirm the X-API-Key header and generate a replacement key if necessary.

404 Not Found

The requested tenant, learner, or course might not exist in the key owner's permitted scope. Reporting routes also return 404 when Hatz Learn reporting is not available to that user identity.

The portfolio is empty

An empty tenants array means the key owner is an MSP operator but has no company in scope for View Academy Team Progress. Confirm that the permission is granted at MSP scope for portfolio reporting and that account-team restrictions include the expected tenants.

Company-only reporting permission can expose that company, but it does not grant access to the rest of the MSP portfolio.

Summary metrics show 0/0

First check whether GET /academy/reporting/portfolio returns any tenants:

  • If the portfolio is empty, correct the key owner's reporting permissions or tenant scope.
  • If the tenant is present with zero denominators, the company might legitimately have no currently eligible learners or applicable published courses.

Raw or historical enrollment totals can differ from coverage metrics because current coverage excludes ineligible learners and non-applicable learner-course pairs.

can_export is false

The key owner does not have both View Academy Team Progress and Export Academy Reports for any company in the requested scope. Grant both permissions in overlapping scope. Creating another key for the same user will not change the result.

403 Forbidden when exporting

The export request has no exportable company in scope. Confirm the two export permissions and, for tenant exports, confirm access to that tenant_id.

422 Unprocessable Entity

The request body or query parameters are invalid, course_summary is missing tenant_id, a portfolio-only report was given tenant_id, or the synchronous export exceeds its tenant or row limit.

Availability notes

Hatz Learn reporting is evaluated for the API-key owner's user identity. A company whose Learn switch is off can still expose historical reporting data as read-only, while current coverage is unavailable. If the reporting routes return 404 even though the key works elsewhere, contact your Hatz account team to confirm availability for that user.