Consulting Queries
How to query consulting services and their time-slot meetings in the Loopwise Admin API
Endpoint and authentication. All admin queries live on
POST /admin/graphql— not the/graphqlendpoint, which is the public school API. Authenticate with ansk_live_…key in theX-Teachify-API-Keyheader. All consulting queries require thecourses:readscope.
Overview
The Consulting API exposes:
- Consulting services — coaching/consulting offerings attached to a course
- Consulting meetings — individual time slots booked under a service
Both resources support paginated list queries with filters, plus singular lookups by ID.
Available Queries
| Query | Returns | Description |
|---|---|---|
consultingServices(filter) | AdminConsultingServicePage! | Paginated list of consulting services |
consultingService(id) | AdminConsultingService | Single service by ID; null if not found |
consultingMeetings(filter) | AdminConsultingMeetingPage! | Paginated list of meetings under consulting services |
consultingMeeting(id) | AdminConsultingMeeting | Single meeting by ID; null if not found or not under a consulting service |
consultingServices
Returns a paginated list of consulting services for the school. Ordered by createdAt DESC, id DESC.
Filter Parameters (AdminConsultingServiceFilter)
| Field | Type | Description |
|---|---|---|
courseId | String | Filter by parent course ID |
slug | String | Exact slug match (school-scoped) |
published | Boolean | Filter by published flag. Checks the boolean column directly, not the time-gated effective state |
keyword | String | Case-insensitive substring match against name or slug |
Example
query ConsultingServices {
consultingServices(filter: { published: true, keyword: "coaching" }) {
nodes {
id
name
slug
published
publishedAt
courseId
lecturerId
lecturer {
id
name
}
effectiveTimezone
meetingCount(state: available)
tags
createdAt
updatedAt
}
currentPage
totalPages
hasNextPage
}
}AdminConsultingService Fields
| Field | Type | Description |
|---|---|---|
id | String! | |
name | String! | Display name |
slug | String! | URL-friendly identifier (school-scoped) |
description | String | |
published | Boolean! | Whether the service is marked as published |
publishedAt | Int | Unix timestamp; null if never published |
tags | [String!] | Tag list stored in settings JSON |
backgroundColor | String | UI background color (hex) |
ratingFormId | String | ID of the linked feedback form, if any |
courseId | String | Parent course ID. Resolve details via the courses admin query |
lecturerId | String | Assigned lecturer ID |
lecturer | AdminLecturer | Assigned lecturer object (preferred over lecturerId for one-shot fetches) |
effectiveTimezone | String! | IANA timezone (e.g. Asia/Taipei). Use when converting local datetimes to Unix timestamps for meeting startedAt / endedAt |
meetingCount(state) | Int! | Number of undiscarded meetings under this service, optionally filtered by AASM state |
discardedAt | Int | Unix timestamp; non-null when soft-deleted |
createdAt | Int! | Unix timestamp |
updatedAt | Int! | Unix timestamp |
consultingService
Returns a single consulting service by ID, scoped to the current school. Returns null if not found or not in this school.
Example
query ConsultingService {
consultingService(id: "35a72dba-c3cc-4a06-b7eb-d784d010386e") {
id
name
slug
effectiveTimezone
meetingCount(state: scheduled)
lecturer {
id
name
}
}
}consultingMeetings
Returns a paginated list of meetings under the school's consulting services. Ordered by startedAt DESC, id DESC.
Filter Parameters (AdminConsultingMeetingFilter)
| Field | Type | Description |
|---|---|---|
serviceId | String | Filter by parent ConsultingService ID |
lecturerId | String | Filter by assigned lecturer ID |
state | MeetingState | AASM state: available, scheduled, or canceled |
startedAfter | Int | Unix timestamp; matches meetings with startedAt >= value |
startedBefore | Int | Unix timestamp; matches meetings with startedAt <= value |
Example
query ConsultingMeetings {
consultingMeetings(filter: {
serviceId: "35a72dba-c3cc-4a06-b7eb-d784d010386e"
state: scheduled
startedAfter: 1747200000
}) {
nodes {
id
title
state
startedAt
endedAt
serviceId
lecturerId
hostUserId
hostEmail
hostingType
joinUrl
maxAttendeeCapacity
attendeeCount
attendees {
id
email
name
}
}
currentPage
totalPages
hasNextPage
}
}AdminConsultingMeeting Fields
| Field | Type | Description |
|---|---|---|
id | String! | |
title | String | Meeting title |
description | String | |
state | String! | AASM state: available, scheduled, canceled |
startedAt | Int | Unix timestamp |
endedAt | Int | Unix timestamp |
serviceId | String | Parent ConsultingService ID. Resolve details via consultingService(id) |
lecturerId | String | Lecturer profile id (slug, URL routing, slot-overlap detection). NOT the person who runs the session — see hostUserId. Resolve via the lecturers admin query |
hostUserId | String | User id (school owner or teaching assistant) of the person who actually hosts the meeting. Sourced from meetings.hosts[0].id; null on legacy rows where no host was selected |
hostEmail | String | Zoom license email used when hostingType: zoom. null for non-Zoom hosting types or when the school had no Zoom license configured |
hostingType | String | zoom, live_session, or custom |
hostingId | String | External hosting platform meeting ID (e.g. Zoom meeting ID) |
joinUrl | String | Pre-set join URL for zoom / custom hosting types |
maxAttendeeCapacity | Int | Maximum students allowed; null or 0 means unlimited |
price | Float | |
currency | String! | |
attendeeCount | Int! | Current number of enrolled students |
attendees | [AdminUser!]! | Enrolled students |
discardedAt | Int | Unix timestamp; non-null when soft-deleted |
createdAt | Int! | Unix timestamp |
updatedAt | Int! | Unix timestamp |
Meeting States
| State | Meaning |
|---|---|
available | Slot is open; no students enrolled (or all have been removed) |
scheduled | At least one student has been confirmed for this slot |
canceled | Slot has been canceled |
consultingMeeting
Returns a single consulting meeting by ID, scoped to the current school. Returns null if not found, not in this school, or not under a consulting service.
Example
query ConsultingMeeting {
consultingMeeting(id: "0186b768-c0c0-4d75-8fb7-7fee0b3ee9c1") {
id
state
startedAt
endedAt
attendeeCount
attendees {
id
email
name
}
}
}meetingHosts
Returns the school owner plus all undiscarded teaching assistants — the exact user pool accepted by hostUserId on bulkCreateConsultingMeetings and updateConsultingMeeting. Use this before writing a meeting to populate UI host pickers or to pre-validate, rather than relying on the MEETING-012 valid_options= recovery suffix after a bad write.
Each row carries an explicit role discriminator (owner or teaching_assistant) — filter by role rather than relying on response ordering when you only want one or the other. The owner is deduplicated to a single role: owner row when they also hold the teaching_assistant role; TAs are returned in user.createdAt ascending order so you can pick the most-tenured by index.
Example
query MeetingHosts {
meetingHosts {
role
user {
id
name
email
createdAt # use to pick "most senior TA" when needed
}
}
}AdminMeetingHost Fields
| Field | Type | Description |
|---|---|---|
role | AdminMeetingHostRole! | owner (the school owner) or teaching_assistant |
user | AdminUser! | The user record. Pass user.id as hostUserId on the mutation |
Notes
- Scope: school-wide. Returns a non-paginated array — the pool is intentionally small (1 owner + N TAs).
- The admin API does NOT gate on the school admin UI's
:learning_centerfeature flag; TAs are always returned regardless of the flag's state. The school admin UI may show a narrower pool. - Required OAuth scope:
courses:read(the same scope as the other consulting queries). - Empty result means the school has no eligible hosts (owner was soft-deleted AND no teaching assistants exist). Consulting meeting mutations will fail
MEETING-012with emptyvalid_options=until either the owner is restored or a teaching assistant is invited via the school admin UI.
zoomHosts
Returns the Zoom license seats configured under the school's active Zoom integration — the exact set of values accepted by hostEmail on consulting meeting mutations when hostingType: zoom. Use this before writing a Zoom meeting, instead of relying on the MEETING-011 valid_options= recovery suffix after a bad write.
Returns an empty list when the school has no active Zoom integration (the same precondition that MEETING-009 enforces on the mutation) or when the integration has no license metadata recorded. Order matches Settings → Integrations → Zoom in the admin UI.
Example
query ZoomHosts {
zoomHosts {
email
name
}
}AdminZoomHost Fields
| Field | Type | Description |
|---|---|---|
email | String! | Zoom license email. Pass as hostEmail on consulting meeting mutations |
name | String | Display name configured for this license seat; null if not set |
Notes
- Scope: school-wide. Required OAuth scope:
courses:read. - Result reflects only the active Zoom integration. Inactive integrations are intentionally hidden so callers can't accidentally provision against a disabled seat.