LogoLogo
WebsiteChangelogBook a Demo
  • Welcome to Peoplelogic.dev
  • API Documentation
    • Page 1
  • Guides
    • Page 2
  • Tutorials
    • OKR Dashboard with Kotlin Client
    • Managing Meetings with Kotlin and OkHttp
    • Using Webhooks for Slack Notifications
Powered by GitBook

2025 Peoplelogic, Inc.

On this page
  • Setting Up OkHttp and Authentication
  • Creating a New Meeting
  • Finding Employees by Email
  • Adding Participants to the Meeting
  • Adding a Meeting Agenda (Note)
  • Adding Tasks for Follow-ups
  • Retrieving and Displaying the Meeting Details
  1. Tutorials

Managing Meetings with Kotlin and OkHttp

In this tutorial, we'll build a small Kotlin command-line tool that interacts with Peoplelogic.dev's REST API to manage meetings. Specifically, we'll demonstrate how to create a meeting and then use various API endpoints to add participants, attach an agenda note, and include follow-up tasks. All interactions will use raw HTTP requests via the OkHttp client (no proprietary SDKs), and we'll authenticate using a JWT API key passed as a Bearer token in the Authorization header. By the end, we'll fetch the meeting with all related data (participants, notes, and tasks) and print it out in a structured, human-readable format in the console.

What we'll cover:

  • Setting up OkHttp with a JWT Bearer token for authentication.

  • Creating a new meeting with a POST request to the /meeting endpoint (providing name, type, etc.).

  • Looking up employees by email via GET /employee/{email} to get their IDs.

  • Adding those employees as meeting participants using POST /meeting/{meetingId}/participant.

  • Attaching a note (agenda) to the meeting using POST /entity/{meetingId}/note.

  • Creating a couple of tasks associated with the meeting via POST /entity/{meetingId}/task.

  • Retrieving the meeting (GET /meeting/{meetingId}) with query projections to include participants, notes, and tasks.

  • Formatting and displaying the collected meeting information in a friendly CLI output.

Throughout, the tone is conversational and code-focused β€” as if we're pair-programming this with a fellow backend developer. Let's dive in!

Setting Up OkHttp and Authentication

First, ensure you have the OkHttp library added to your project (e.g., via Gradle). We'll use OkHttp to perform HTTP calls. We also assume you have a Peoplelogic API JWT key ready (obtainable from your Peoplelogic account). We'll use this JWT for authenticating our requests by sending it as a Bearer token.

Let's configure an OkHttp client that automatically includes the Authorization: Bearer <API_KEY> header on every request. This saves us from adding the header manually to each request:

import okhttp3.OkHttpClient
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response

val API_KEY = "<YOUR_PEOPLELOGIC_JWT>"  // replace with your actual API key (JWT)
val client = OkHttpClient.Builder()
    .addInterceptor(Interceptor { chain ->
        val originalRequest = chain.request()
        // Inject the Authorization header into the request
        val requestWithAuth: Request = originalRequest.newBuilder()
            .header("Authorization", "Bearer $API_KEY")
            .build()
        chain.proceed(requestWithAuth)
    })
    .build()

In the above code, we build an OkHttpClient with an interceptor that appends our JWT API key as a Bearer token in the Authorization header for all requests. This way, we don't have to set it by hand each time. Now we're ready to start making authenticated calls to the Peoplelogic API.

(Note: For simplicity, we'll use Kotlin's standard library and org.json for JSON parsing in this tutorial. In a real project, you might use a library like kotlinx.serialization or Moshi for JSON, but org.json will suffice for illustrative purposes.)

Creating a New Meeting

Let's begin by creating a meeting. Peoplelogic provides a REST endpoint to create meetings via POST to /meeting. The request body should include at least a name for the meeting, the type of meeting, and whether it's recurring or not. The meeting type is an enum with options like INTERNAL, EXTERNAL, or ONE_ON_ONE, indicating what kind of meeting it is (internal team meeting, external meeting with a client, one-on-one, etc.). For our example, we'll create an internal meeting that is not recurring.

We'll construct a JSON payload with the meeting details and send the POST request. The Peoplelogic API expects JSON, so we'll set the appropriate Content-Type header and include the JWT (which our client will handle). Here's how we can do it:

import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject

val baseUrl = "https://api.peoplelogic.dev/api/v1"  // base URL for Peoplelogic API (v1)
val meetingJson = """
{
  "name": "Project Roadmap Discussion",
  "type": "INTERNAL",
  "recurring": false
}
""".trimIndent()
val mediaTypeJson = "application/json; charset=utf-8".toMediaType()
val requestBody = meetingJson.toRequestBody(mediaTypeJson)

val createMeetingRequest = Request.Builder()
    .url("$baseUrl/meeting")
    .post(requestBody)
    .build()

val createMeetingResponse = client.newCall(createMeetingRequest).execute()
if (!createMeetingResponse.isSuccessful) {
    error("Failed to create meeting: ${createMeetingResponse.code}")
}
val responseBody = createMeetingResponse.body!!.string()
// Parse the response to get the meeting ID (and other info if needed)
val responseJson = JSONObject(responseBody)
val meetingObj = responseJson.optJSONObject("meeting") ?: responseJson  // handle wrapped response
val meetingId = meetingObj.getString("id")
val meetingName = meetingObj.getString("name")
println("βœ… Created meeting '$meetingName' with ID $meetingId")

In this code, we build the JSON string for the meeting (with name, type, and recurring fields) and send it via a POST request to the /meeting endpoint. We then check for a successful response (HTTP 200/201) and parse the JSON response to extract the newly created meeting's ID. Peoplelogic returns the created meeting object; we grab its id (which we'll need for subsequent calls) and also log the name for confirmation. We used JSONObject to parse the response; if the API responds with the meeting wrapped in a top-level "meeting" JSON object, we handle that by using optJSONObject("meeting") (if not, we just parse the root as the meeting).

At this point, we have a new meeting in Peoplelogic with a known ID. Next, let's add participants to this meeting.

Finding Employees by Email

Suppose we want to invite certain employees to this meeting. Peoplelogic identifies employees by UUID internally, but we might only know their emails (for example, from our company directory). The API provides a convenient GET endpoint to retrieve an employee by their email address: GET /employee/{email}. We can use this to look up each participant and get their id which will be needed to add them to the meeting.

Let's say we have two employees we want to add: Alice and Bob, identified by their email addresses. We'll fetch each one by email:

val participantEmails = listOf("alice@example.com", "bob@example.com")
val participantIds = mutableListOf<String>()
val idToName = mutableMapOf<String, String>()  // to store names for output later
val idToEmail = mutableMapOf<String, String>()

for (email in participantEmails) {
    val getEmployeeRequest = Request.Builder()
        .url("$baseUrl/employee/$email")
        .get()
        .build()
    val response = client.newCall(getEmployeeRequest).execute()
    if (!response.isSuccessful) {
        error("Employee lookup failed for $email (HTTP ${response.code})")
    }
    val body = response.body!!.string()
    val empJson = JSONObject(body)
    val empObj = empJson.optJSONObject("employee") ?: empJson
    val employeeId = empObj.getString("id")
    val employeeName = empObj.getString("name")
    participantIds.add(employeeId)
    idToName[employeeId] = employeeName
    idToEmail[employeeId] = email
    println("πŸ”Ž Found employee $employeeName (ID=$employeeId) for email $email")
}

Here we loop through each participant email, send a GET request to /employee/{email}, and parse the result. On success, the API returns the employee's data (which includes their unique id and name, among other fields). We collect each employee's ID in a list (participantIds) and also keep a mapping of ID to name (and email) for later, so we can easily print names instead of raw IDs. The println is just for our confirmation that we found the right people. We now have the IDs of "Alice" and "Bob" as participantIds[0] and participantIds[1] respectively.

(Note: This step assumes the employees already exist in Peoplelogic. In practice, if an email isn't found (404 response), you'd need to onboard that employee via the API's employee creation endpoint. For our tutorial, we'll assume Alice and Bob are already in the system.)

Adding Participants to the Meeting

With the meeting created and the employee IDs in hand, we can now add these employees as participants of the meeting. The Peoplelogic API provides a POST endpoint /meeting/{meetingId}/participant that lets you assign an employee (or external guest) to a meeting. We need to send a JSON body containing an assignment specifying the participant type and the reference to the participant (either an employeeId for internal users, or an email/name for external participants).

Peoplelogic supports different participant roles via a type field in the assignment: for example, PRIMARY might indicate the meeting organizer/host, SECONDARY for regular internal participants, OBSERVER for non-participating observers, and EXTERNAL for guests outside the organization. We’ll designate the first employee (Alice) as the primary host and the second (Bob) as a secondary participant.

Let's add them to our meeting by making two POST requests to the participant endpoint:

// Assume participantIds[0] is Alice (host) and participantIds[1] is Bob (guest)
val (hostId, guestId) = participantIds

// 1. Add Alice as the PRIMARY participant (host)
val hostAssignJson = """{"type": "PRIMARY", "employeeId": "$hostId"}"""
val hostReqBody = hostAssignJson.toRequestBody(mediaTypeJson)
val addHostRequest = Request.Builder()
    .url("$baseUrl/meeting/$meetingId/participant")
    .post(hostReqBody)
    .build()

val addHostResponse = client.newCall(addHostRequest).execute()
if (!addHostResponse.isSuccessful) {
    error("Failed to add host (HTTP ${addHostResponse.code})")
}

// 2. Add Bob as a SECONDARY participant (regular attendee)
val guestAssignJson = """{"type": "SECONDARY", "employeeId": "$guestId"}"""
val guestReqBody = guestAssignJson.toRequestBody(mediaTypeJson)
val addGuestRequest = Request.Builder()
    .url("$baseUrl/meeting/$meetingId/participant")
    .post(guestReqBody)
    .build()

val addGuestResponse = client.newCall(addGuestRequest).execute()
if (!addGuestResponse.isSuccessful) {
    error("Failed to add participant (HTTP ${addGuestResponse.code})")
}

println("βœ… Added ${idToName[hostId]} as host and ${idToName[guestId]} as participant to meeting $meetingId")

We post two assignment requests to /meeting/{meetingId}/participant. The JSON for each includes the type of participant and the employeeId of the person we're adding. In our case, we send {"type": "PRIMARY", "employeeId": "<Alice's UUID>"} for the host, and {"type": "SECONDARY", "employeeId": "<Bob's UUID>"} for the other participant. The API will link those employees to the meeting. If successful, each call returns an updated meeting object including the new participant, but we don't necessarily need to parse the response here since we'll fetch the full meeting details later. We just verify the status and print a confirmation. (Under the hood, the API now considers Alice and Bob as assignees on the meeting, which is how it tracks participants.)

Adding a Meeting Agenda (Note)

Now that our meeting has participants, let's add an agenda. Peoplelogic's platform allows attaching notes to entities (meetings, tasks, etc.), which can serve as agendas, minutes, or any text content. We will use the POST /entity/{meetingId}/note endpoint to add a note to our meeting. The request body should include the note details. According to Peoplelogic's note model, a note has a name (title), content (the body of the note), and a visibility setting, among other fields. It also supports a noteType to classify the note (for example, a note could be categorized as an INTERNAL_MEETING note, EXTERNAL_MEETING note, etc., which is useful for filtering or analytics). We'll make this note the meeting's agenda, so we can give it a name like "Meeting Agenda" and include the agenda content.

We should also specify the visibility. Peoplelogic notes have visibility statuses (Public, Limited, Private). For an agenda that all meeting participants should see, PUBLIC is appropriate (which usually means it's visible to the organization or at least to all involved). Additionally, we can set the note's owner to the host of the meeting (so it’s attributed to Alice, for example). Let's add the agenda note:

val agendaNoteJson = """
{
  "name": "Meeting Agenda",
  "content": "Discuss project roadmap and milestones for Q3.",
  "noteType": "INTERNAL_MEETING",
  "format": "text",
  "visibility": "PUBLIC",
  "owner": "$hostId"
}
""".trimIndent()
val noteRequestBody = agendaNoteJson.toRequestBody(mediaTypeJson)
val addNoteRequest = Request.Builder()
    .url("$baseUrl/entity/$meetingId/note")
    .post(noteRequestBody)
    .build()

val addNoteResponse = client.newCall(addNoteRequest).execute()
if (!addNoteResponse.isSuccessful) {
    error("Failed to add agenda note (HTTP ${addNoteResponse.code})")
}
println("βœ… Added agenda note to meeting $meetingId")

We constructed a JSON with the agenda details: a title ("Meeting Agenda"), some content, a noteType of INTERNAL_MEETING (since this is an internal meeting's note), and visibility: "PUBLIC" so it's broadly visible. We also include "owner": "<hostId>" to explicitly set the note's owner as Alice (the host). After posting to /entity/{meetingId}/note, the Peoplelogic API will create the note and associate it with our meeting. A successful response means the agenda is now stored. (Like before, the API would return the updated meeting or note info, but we can skip parsing it immediately.)

Adding Tasks for Follow-ups

Next, let's add a couple of tasks related to this meeting. Perhaps during the meeting, we anticipate some follow-up actions. Peoplelogic lets you attach tasks to any entity in a similar fashion to notes. The endpoint is POST /entity/{meetingId}/task for creating a task under the meeting. Each task needs at least a name (what the task is), and it can also have a description and a dueDate, among other fields. For simplicity, we'll add two tasks with just names and brief descriptions (no due dates).

For example, let's add one task for someone to follow up with the design team after the meeting, and another task to prepare a budget draft. We'll send two POST requests, one for each task:

// Task 1: Follow up with design team
val task1Json = """
{
  "name": "Follow up with design team",
  "description": "Email the meeting summary to the design lead"
}
""".trimIndent()
val task1Request = Request.Builder()
    .url("$baseUrl/entity/$meetingId/task")
    .post(task1Json.toRequestBody(mediaTypeJson))
    .build()
val task1Response = client.newCall(task1Request).execute()
if (!task1Response.isSuccessful) {
    error("Failed to add task 1 (HTTP ${task1Response.code})")
}

// Task 2: Prepare budget draft
val task2Json = """
{
  "name": "Prepare budget draft",
  "description": "Outline budget for Q3 projects"
}
""".trimIndent()
val task2Request = Request.Builder()
    .url("$baseUrl/entity/$meetingId/task")
    .post(task2Json.toRequestBody(mediaTypeJson))
    .build()
val task2Response = client.newCall(task2Request).execute()
if (!task2Response.isSuccessful) {
    error("Failed to add task 2 (HTTP ${task2Response.code})")
}

println("βœ… Added 2 tasks to meeting $meetingId")

Each task is added by posting a JSON with a name and description to the /entity/{meetingId}/task endpoint. We check that each request succeeded. After these calls, our meeting now has two tasks associated with it in Peoplelogic's system.

(Just like notes, each task creation returns the updated meeting with the new task or the task object itself. We omitted parsing those responses here for brevity, as we'll retrieve everything in one go next.)

Retrieving and Displaying the Meeting Details

We have done all the operations: created a meeting, added participants, an agenda note, and tasks. Now it's time to fetch the meeting data and see if everything is in place. We want the meeting details along with its participants, notes, and tasks. By default, a GET on the meeting might not include all these related items, but Peoplelogic's API allows us to request projections – essentially telling the API to include those related collections in the response.

To do this, we add a query parameter projections to our GET request. For example, to include participants (internally called assignees), notes, and tasks, we can specify ?projections=ASSIGNEES,NOTES,TASKS in the URL. Each of these projection keywords corresponds to a set of related data:

  • ASSIGNEES will include the list of participants (assignees) in the meeting.

  • NOTES will include any notes attached to the meeting (like our agenda).

  • TASKS will include the tasks associated with the meeting.

Peoplelogic expects the projection names as a comma-separated list in the query string. Let's call GET on the meeting with these projections:

val getMeetingRequest = Request.Builder()
    .url("$baseUrl/meeting/$meetingId?projections=ASSIGNEES,NOTES,TASKS")
    .get()
    .build()

val getMeetingResponse = client.newCall(getMeetingRequest).execute()
if (!getMeetingResponse.isSuccessful) {
    error("Failed to fetch meeting details (HTTP ${getMeetingResponse.code})")
}
val meetingDetailsJson = JSONObject(getMeetingResponse.body!!.string())
val fullMeetingObj = meetingDetailsJson.optJSONObject("meeting") ?: meetingDetailsJson

// Extract main meeting info
val name = fullMeetingObj.getString("name")
val type = fullMeetingObj.getString("type")
val recurring = fullMeetingObj.getBoolean("recurring")

println("Meeting: $name (Type: $type, Recurring: $recurring)")

// List out participants (assignees)
println("Participants:")
val assigneesArray = fullMeetingObj.getJSONArray("assignees")
for (i in 0 until assigneesArray.length()) {
    val participant = assigneesArray.getJSONObject(i)
    // Determine participant's name and email:
    // If it's an external participant, the object may have 'name' or 'email' fields directly.
    // If it's an internal employee, we use the stored maps (idToName/Email) via the assignee's id.
    var participantName = participant.optString("name", "")
    var participantEmail = participant.optString("email", "")
    if (participantName.isEmpty()) {
        val assigneeInfo = participant.optJSONObject("assignee")
        if (assigneeInfo != null && assigneeInfo.getString("type") == "EMPLOYEE") {
            val empId = assigneeInfo.getString("entityId")
            participantName = idToName[empId] ?: "(Employee $empId)"
            participantEmail = idToEmail[empId] ?: ""
        }
    }
    // Identify role (PRIMARY vs SECONDARY, etc.)
    val roleType = participant.getString("type")
    val roleLabel = if (roleType == "PRIMARY") " (Host)" else ""
    // Print "Name <email>" plus role if host
    val emailPart = if (participantEmail.isNotBlank()) " <$participantEmail>" else ""
    println(" - $participantName$emailPart$roleLabel")
}

// Show the agenda note(s)
println("Agenda:")
val notesArray = fullMeetingObj.getJSONArray("notes")
if (notesArray.length() > 0) {
    val agendaNote = notesArray.getJSONObject(0)  // we only added one note
    val agendaContent = agendaNote.optString("content", "(no content)")
    println(" - $agendaContent")
} else {
    println(" (No notes)")
}

// Show tasks
println("Tasks:")
val tasksArray = fullMeetingObj.getJSONArray("tasks")
if (tasksArray.length() > 0) {
    for (i in 0 until tasksArray.length()) {
        val task = tasksArray.getJSONObject(i)
        val taskName = task.getString("name")
        val taskDesc = task.optString("description", "")
        if (taskDesc.isNotBlank()) {
            println(" - $taskName β€” $taskDesc")
        } else {
            println(" - $taskName")
        }
    }
} else {
    println(" (No tasks)")
}

Let's break down what we're doing in this final step:

  • We send a GET request to /meeting/{meetingId} with ?projections=ASSIGNEES,NOTES,TASKS to ask for participants, notes, and tasks to be included in the response. The API responds with a JSON object for the meeting that now contains arrays for "assignees" (participants), "notes", and "tasks" in addition to the basic meeting fields.

  • We parse the response into a JSONObject. As before, if the API wraps the result in a "meeting" key, we account for that.

  • We extract the meeting's name, type, and recurring flag and print them in one line for a header.

  • For Participants: we iterate over the assignees array. Each participant entry includes a type (Primary, Secondary, etc.) and either an embedded reference to an employee or direct name/email if it was an external invite. For internal employees like Alice and Bob, the entry might not contain their names/emails directly (it will have an assignee object with their ID and type). We use the idToName/idToEmail maps we built earlier to resolve those IDs back to actual names/emails for display. We then print each participant in the format "Name <email>" and append "(Host)" for the one with type PRIMARY.

  • For Agenda: we take the first note in the notes list (we only added one note, which is our agenda). We print its content. If there were multiple notes, we could list them, but in this case, it's just the agenda.

  • For Tasks: we loop through the tasks array. For each task, we print the name and, if a description was provided, we print an em dash and the description after the name. Each task shows up as a bullet under Tasks.

Finally, when we run the above code, we should see a nicely formatted summary of the meeting in our console. For example:

Meeting: Project Roadmap Discussion (Type: INTERNAL, Recurring: false)
Participants:
 - Alice Johnson <alice@example.com> (Host)
 - Bob Smith <bob@example.com>
Agenda:
 - Discuss project roadmap and milestones for Q3.
Tasks:
 - Follow up with design team β€” Email the meeting summary to the design lead
 - Prepare budget draft β€” Outline budget for Q3 projects

(The output above shows that our meeting was created correctly with the expected details and associations. Alice is marked as the host, Bob as a participant, the agenda content is listed, and the two tasks are displayed with their descriptions.)


Conclusion: In this tutorial, we demonstrated how to use Kotlin and OkHttp to work with Peoplelogic.dev's REST API for meeting management. We covered authenticating with a JWT, creating a meeting via REST, looking up employees by email, adding participants (with different roles), posting an agenda note, and adding follow-up tasks. We also retrieved the enriched meeting data in one call using projections and printed the information in a readable format.

This developer-to-developer walkthrough should help you integrate Peoplelogic's meeting endpoints into your own Kotlin applications. Feel free to expand on this example by handling errors more robustly, assigning tasks to specific people, or exploring other features of the Peoplelogic API as needed. Happy coding!

PreviousOKR Dashboard with Kotlin ClientNextUsing Webhooks for Slack Notifications

Last updated 5 days ago