Tools
hackÜ exposes 22 tools. All of them are read-only except one
—request_report— which orders a report and sends it to you by email.
You do not need to call them by name: you ask the assistant in plain language and it picks which one to use. This page is the reference for what it can answer and within what limits, useful for knowing whether something is possible before you ask for it.
Context
Section titled “Context”get_company_overview
Section titled “get_company_overview”Company overview
users:read
read only
The company’s headline numbers in one call: people, catalogue, seats, review queue.
Start here for “how are we doing” instead of paging through
list_users. One call, a handful of counts.
membership.unlimited_seats true means no seat limit was ever set —
the stored limit is a placeholder, so do not quote a seat number.
If segment_restriction is set, the people counts are that staff
member’s slice, not the company. Say so rather than presenting them as
company totals.
Parameters: none.
get_context
Section titled “get_context”Identify the connected company
no permission (identifies the connection) read onlyWhich company this credential reads, as whom, and with what limits.
Call this first, before answering anything about “our company”, “us”, or “the users”. Every other tool is already scoped to a single company but none of them name it, so without this the only honest answer to “what company are we?” is that you do not know — while holding a credential that knows exactly.
It also reports the limits worth stating out loud in an answer:
access.pii_availablefalse means names, emails and phones are withheld by configuration. Rows still come back, identified by id.access.segment_restriction, when set, means this staff member sees only one slice of the company. Every count and total is of that slice. Say so rather than presenting it as the company’s total.access.scopesis what this credential may read. A tool that is missing from it will refuse, and that is configuration, not an error to retry.
Costs one small query and needs no arguments.
Parameters: none.
People
Section titled “People”list_users
Section titled “list_users”List company users
users:read
read only
List the people enrolled in your company on hackU, with their current course, module and progress. Supports search and filtering, so prefer narrowing the query over paging through everyone.
Scope: returns only your own company. There is no parameter to read another company’s people — the company is fixed by the credential.
Search: search matches a name, email, phone number or document number, whole or partial. Use it when the question is about a specific person.
Filtering: status is one of not_started, in_progress, completed, no_data. min_progress / max_progress take percentages 0-100. To answer ‘who is falling behind’, filter — do not fetch everyone and sort.
Pagination: returns ONE PAGE, not everyone. When has_more is true there are further users you have not seen; pass next_cursor back as cursor to continue. Never describe one page as the full roster, and never infer a total from the number of rows returned — pass include_total=true if you need the real count.
Missing data: has_progress_data: false (status no_data) means hackU has no calculated progress for that person yet. That is NOT the same as 0% progress — report it as unknown, not as ‘has not started’.
Personal data: names, emails, phone and document numbers appear only when the deployment allows it and the credential carries the users:pii scope. Check pii_included; when it is false you have user ids only, and you should refer to people by id rather than guessing.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int |
25 |
rows per page. The server caps this; asking for more returns the cap. |
cursor |
`str | None` | None |
include_total |
bool |
False |
also return how many people match. Off by default because counting the whole matching set costs more than reading one page. |
search |
`str | None` | None |
status |
`str | None` | None |
min_progress |
`float | None` | None |
max_progress |
`float | None` | None |
get_user
Section titled “get_user”Get one company user
users:read
read only
The full profile of one person in your company — the same picture the hackU corporate dashboard shows across its tabs.
Returns: their course, module, pensum and progress; activity with contents sent, contents viewed and hours used; points with the total and a breakdown by what earned them; certificates issued; pensums enrolled in; engagement with comments, likes and replies; and extra_fields, the company’s own custom fields for that person.
Takes the user_id from a list_users result. If you only have a name, an email or a phone number, call list_users with search first — this tool does not look people up by name.
One person at a time. To compare or rank several, use list_users with filters rather than calling this in a loop.
Counts here are lifetime totals across the person’s whole history, not the current course. activity.contents_viewed can exceed what their active route contains, and that is not an error.
A user id belonging to another company returns 404, the same as one that does not exist. Do not read that as evidence the person exists elsewhere.
Personal data follows the same rule as list_users: check pii_included.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
user_id |
int |
(required) | the user_id from a list_users row. |
Courses
Section titled “Courses”list_courses
Section titled “list_courses”List courses
courses:read
read only
The course catalogue of your company: what is being taught, with how many modules and contents each course has.
This is about the courses themselves, not about who is taking them. For people and their progress use list_users.
Archived courses are excluded unless you pass include_archived=true. A question like ‘what courses do we have’ means the live ones, and counting archived ones silently would inflate every answer.
Courses carry both a name and an alias: the alias is what the company renamed it to for its own people, so prefer it when it is set — that is the name a staff member will recognise.
Paginated the same way as list_users: follow next_cursor, and pass include_total=true rather than counting the rows you got.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int |
25 |
rows per page. The server caps this. |
cursor |
`str | None` | None |
search |
`str | None` | None |
include_archived |
bool |
False |
include courses that were archived. Off by default. |
only_published |
bool |
False |
only courses currently published to students. |
include_total |
bool |
False |
also return how many courses match. |
get_course
Section titled “get_course”Get one course
courses:read
read only
One course with its modules in the order a student walks them, and optionally the contents inside each module.
Takes the course_id from a list_courses result. If you only have a course name, call list_courses with search first.
include_contents=true also returns every content inside every module, with its type and position. Leave it off when the question is about structure — it is a much larger answer.
position is the order the module or content is delivered in, not an id. Two courses can both have a module at position 1.
A course id from another company returns 404, the same as one that does not exist.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
course_id |
int |
(required) | the course_id from a list_courses row. |
include_contents |
bool |
False |
also return the contents inside each module. |
Communications
Section titled “Communications”list_communications
Section titled “list_communications”List communications
communications:read
read only
The communications (campaigns) your company has prepared or sent, with what they said and when they were scheduled.
campaign_dispatched means the campaign was dispatched. It does NOT mean anyone received it — for that, use list_communication_deliveries on the campaign.
No recipient counts here on purpose: counting the audience of every campaign on a page is expensive, and capping the page does not cap their audiences.
Paginated: follow next_cursor, and pass include_total=true rather than counting the rows you got.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int |
25 |
rows per page; the server caps this. |
cursor |
`str | None` | None |
search |
`str | None` | None |
only_sent |
bool |
False |
only campaigns already dispatched. |
include_total |
bool |
False |
also return how many match. |
get_communication
Section titled “get_communication”Get one communication
communications:read
read only
One communication in full: both message bodies, its buttons, its template, its channel and when it was scheduled.
Takes the communication_id from a list_communications result. For who received it and what happened to each send, use list_communication_deliveries.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
communication_id |
int |
(required) | the communication_id from a list_communications row. |
list_communication_deliveries
Section titled “list_communication_deliveries”List deliveries of a communication
communications:read
read only
Who a communication went to, and what the messaging provider recorded for each recipient: whatsapp_sent, whatsapp_delivered, whatsapp_read, whatsapp_failed, sms_sent.
READ THIS BEFORE REPORTING ANY OF IT. These flags are evidence, not a state machine, and they are NOT mutually exclusive: the provider raises one without clearing the others, so the same recipient can be both failed and delivered after a retry. Report what is recorded; never collapse them into a single final status.
A false means no evidence of that event was stored. It is NOT proof the event did not happen — a provider that never returns read receipts looks exactly like a message nobody opened. Say ‘no read receipt’ rather than ‘nobody read it’.
send_process_closed is about hackU finishing the send attempt; some errors close it too. It is not delivery.
Filter with status: sent, delivered, read, failed, pending, no_message. Each selects the presence of one piece of evidence, never the absence of another.
Do not compute a delivery rate by subtracting failures from sends. That is the formula the dashboard uses and it does not mean successful delivery.
Personal data follows the usual rule: check pii_included. When present, sent_to_phone and sent_to_email are the addresses the message actually went to, which is what matters when asking why one did not arrive.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
communication_id |
int |
(required) | from a list_communications row. |
limit |
int |
25 |
rows per page; the server caps this. |
cursor |
`str | None` | None |
status |
`str | None` | None |
include_total |
bool |
False |
also return how many recipients match the filter. |
include_breakdown |
bool |
False |
also return the counts of every bucket for the whole campaign, regardless of status. |
Evaluations
Section titled “Evaluations”list_evaluations
Section titled “list_evaluations”List evaluations
learning:read
read only
Which evaluations this company’s people have actually sat.
Start here before list_evaluation_attempts, which needs an id to
narrow by. attempts counts sittings and people counts people: a
repeatable evaluation makes those differ, so never read attempts as a
headcount. Grades are on a 0-5 scale.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int |
25 |
— |
list_evaluation_attempts
Section titled “list_evaluation_attempts”List evaluation attempts
learning:read
read only
Individual sittings of an evaluation, narrowed by evaluation or person.
You MUST pass evaluation_id or user_id. One company has a quarter of
a million attempts, so there is no “list them all”; get an id from
list_evaluations or list_users first.
include_summary=true returns averages over the whole narrowed set in
one aggregate — use it instead of paging through attempts to compute an
average yourself.
passed is the platform’s verdict and depends on the evaluation’s own
pass mark. Somebody can get four of five right and still not pass, so do
not recompute it from the question counts.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
evaluation_id |
`int | None` | None |
user_id |
`int | None` | None |
limit |
int |
25 |
— |
cursor |
`str | None` | None |
only_completed |
bool |
False |
— |
passed |
`bool | None` | None |
include_total |
bool |
False |
— |
include_summary |
bool |
False |
— |
get_evaluation_attempt
Section titled “get_evaluation_attempt”Get evaluation attempt
learning:read
read only
One sitting, question by question, with which were right.
The answer each person typed comes back only when the connection may show personal data. Question text is course material and always comes back.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
attempt_id |
int |
(required) | — |
Surveys
Section titled “Surveys”list_surveys
Section titled “list_surveys”List surveys
learning:read
read only
The surveys used by this company’s courses.
A survey is a SHARED object: the same one is attached to the courses of
several companies. These are the ones your courses use. Response counts
are not here for that reason — ask get_survey_results, which filters
to your own people and says so.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int |
25 |
— |
cursor |
`str | None` | None |
search |
`str | None` | None |
only_active |
bool |
False |
— |
include_total |
bool |
False |
— |
get_survey_results
Section titled “get_survey_results”Get survey results
learning:read
read only
How this company’s people answered one survey, question by question.
Every percentage counts ONLY this company’s responses. The payload also
reports responses_all_companies, which is larger because the survey is
shared. Report the first number as the company’s result; never quote the
second as theirs.
Free-text answers are counted but never returned.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
survey_id |
int |
(required) | — |
Pensums
Section titled “Pensums”list_pensums
Section titled “list_pensums”List pensums
learning:read
read only
The company’s programmes: ordered sets of courses people move through.
A pensum is not a course. list_courses is the catalogue; this is how
courses are bundled into a path with an expected pace.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int |
25 |
— |
cursor |
`str | None` | None |
search |
`str | None` | None |
only_active |
bool |
False |
— |
only_published |
bool |
False |
— |
include_total |
bool |
False |
— |
get_pensum
Section titled “get_pensum”Get pensum
learning:read
read only
One programme: its courses in order, and how enrolment is going.
To answer “are they on track?”, compare enrolment.average_progress
against enrolment.average_theoretical_progress — the second is what
the schedule expected by now. The gap is the answer; neither number
alone is.
Before reading a 0% as “has not started”, check pensum.courses: a
pensum with no courses keeps its last stored progress rather than being
recomputed, so a zero there means nothing is configured.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
pensum_id |
int |
(required) | — |
list_pensum_enrolments
Section titled “list_pensum_enrolments”List pensum enrolments
learning:read
read only
Who is in one programme and how far along each person is.
Returns ONE PAGE. To answer “how many are behind”, prefer get_pensum’s
aggregate over paging through everyone and counting.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
pensum_id |
int |
(required) | — |
limit |
int |
25 |
— |
cursor |
`str | None` | None |
only_active |
bool |
False |
— |
include_removed |
bool |
False |
— |
include_total |
bool |
False |
— |
Challenges
Section titled “Challenges”list_challenges
Section titled “list_challenges”List challenges
learning:read
read only
Challenges people handed in, and what is still waiting on a reviewer.
reviewed and approved are NOT the same thing. Reviewed means a
person looked at it; approved means they passed it. A reviewed but not
approved challenge is a rejection and is finished — counting it as
pending overstates the queue.
For “what do I owe my team”, use reviewed=false, or
include_summary=true which returns the whole breakdown in one query.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int |
25 |
— |
cursor |
`str | None` | None |
reviewed |
`bool | None` | None |
approved |
`bool | None` | None |
include_total |
bool |
False |
— |
include_summary |
bool |
False |
— |
Ranking
Section titled “Ranking”list_ranking
Section titled “list_ranking”List ranking
users:read
read only
The company’s leaderboard by points, in rank order.
Paged by offset, because a leaderboard is read from the top. Each row
carries its own stored position, computed over the whole company, so
it stays correct whichever slice you asked for — never renumber rows
from their order in the page.
The ranking is produced by a separate job. This reads the stored result, so it is as fresh as that job’s last run, not as of this moment.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int |
25 |
— |
offset |
int |
0 |
— |
include_total |
bool |
False |
— |
Bulk uploads
Section titled “Bulk uploads”list_bulks
Section titled “list_bulks”List bulk uploads
users:read
read only
Spreadsheet uploads of people, and whether any got stuck.
The flags are stages, not one status: a file is validated, then processed, then its messages go out.
stuck: true is the one that matters — the file was accepted and never
processed, so those people were never enrolled and nobody was told. Use
stuck_only=true to answer “did anything fail to load?”.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int |
25 |
— |
cursor |
`str | None` | None |
stuck_only |
bool |
False |
— |
include_total |
bool |
False |
— |
Reports
Section titled “Reports”list_reports
Section titled “list_reports”List generated reports
reports:generate
read only
Spreadsheets this staff member has asked for, and whether they are ready.
download_url needs their own hackU login. It is not public and you
cannot fetch it — give them the link, do not try to read the file.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int |
25 |
— |
request_report
Section titled “request_report”Request a report
reports:generate
writes data
Ask hackU to build a spreadsheet of this company’s data.
THE ONLY TOOL HERE THAT IS NOT A READ. It starts a background job and emails the staff member a download link. Ask them before calling it, and call it once: the job is heavy and takes minutes.
You will not see the contents. That is deliberate — the personal data goes to the person’s own inbox and their own authenticated download, not through this conversation. Do not offer to read the file back.
report_type is one of: users, users_csv, evaluations,
evaluations_detail, surveys, ssaa, point_logs, pensum_users,
finished_courses.
If it answers that one is already being built, that is not an error and not a reason to retry — tell them to wait for the one in flight.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
report_type |
str |
(required) | — |
