In this tutorial, we’ll build a small Kotlin command-line program that interacts with the Peoplelogic.dev API to manage OKRs (Objectives and Key Results) using pure HTTP calls. We’ll cover the following steps:
Authentication: Using a JWT API key as a Bearer token for authorization.
Create an Objective: Making a REST call to create a top-level OKR (Objective).
Add Key Results: Creating child objectives (key results) under the main objective.
Add Tasks: Attaching action-item tasks to the OKRs.
Fetch & Display: Retrieving all OKRs and printing them in a hierarchical, dashboard-like view in the console.
We’ll use Gradle to set up a Kotlin project (JVM) with OkHttp for HTTP requests and a simple JSON library for parsing. The style here is hands-on and explanatory (in the spirit of CrewAI and Spring AI tutorials), with plenty of comments and clarity for a backend engineer with moderate Kotlin experience.
Note: Replace placeholders (like <YOUR_ORG_ID> and <YOUR_API_KEY>) with your actual Peoplelogic organization ID and API key. You can obtain these from your Peoplelogic account. The API key is a JWT token that includes your org credentials and must be included as a Bearer token in each request.
Project Setup
Let's start by setting up a new Kotlin project with Gradle. We will need the Kotlin JVM plugin, and dependencies for OkHttp (for HTTP calls) and org.json (for JSON handling).
Gradle Build Script (build.gradle.kts): This configures the project and dependencies.
plugins {
kotlin("jvm") version "1.8.20"
application
}
group = "com.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("org.json:json:20211205")
}
application {
mainClass.set("com.example.MainKt")
}
A few notes on the setup:
We apply the Kotlin JVM plugin and set a Kotlin version (1.8.20 here).
Dependencies include OkHttp 4.x for HTTP requests and org.json for simple JSON parsing. Both will be pulled from Maven Central.
We use the Gradle Application plugin to easily run the program (with MainKt as the entry point).
The complete source code is provided in the attached ZIP file at the end of this tutorial.
Authenticating with a JWT Bearer Token
The Peoplelogic API expects each request to be authenticated with a JWT token in the Authorization header. In our code, we’ll store the Org ID and API key (JWT) as constants and include them in every request.
The base URL for the Peoplelogic API is https://api.peoplelogic.dev/api/v1. All our endpoint paths will be relative to this (e.g. /objective for OKRs). The JWT token provided by Peoplelogic should be included as a Bearer token in the HTTP Authorization header.
We’ll set up some constants and an OkHttp client in our Main.kt:
package com.example
import okhttp3.*
import org.json.JSONObject
fun main() {
// Placeholder credentials (replace with your actual values)
val ORG_ID = "<YOUR_ORG_ID>"
val API_KEY = "<YOUR_API_KEY>" // JWT token for your Peoplelogic API
val BASE_URL = "https://api.peoplelogic.dev/api/v1"
// Initialize HTTP client
val client = OkHttpClient()
// Media type for JSON payloads
val JSONMedia = MediaType.get("application/json; charset=utf-8")
...
}
Here we set API_KEY to your JWT token string. We’ll use this in the header of every request like so:
.header("Authorization", "Bearer $API_KEY")
This ensures the Peoplelogic API recognizes our calls as authorized.
1. Creating a Top-Level Objective
An Objective in Peoplelogic represents a high-level goal (for example, “Increase Q3 Sales by 20%”). We’ll create a new objective via an HTTP POST request to the /objective endpoint.
Endpoint:POST /objective (requires auth) – Creates a new objective in your organization. The request body is JSON with the objective details.
// 1. Create a top-level Objective (OKR)
val objectiveJson = JSONObject(mapOf(
"name" to "Increase Q3 Sales by 20%",
"description" to "Company-wide sales growth goal for Q3"
))
val createObjectiveRequest = Request.Builder()
.url("$BASE_URL/objective")
.header("Authorization", "Bearer $API_KEY")
.post(RequestBody.create(JSONMedia, objectiveJson.toString()))
.build()
val createObjectiveResponse = client.newCall(createObjectiveRequest).execute()
if (!createObjectiveResponse.isSuccessful) {
println("Failed to create objective: HTTP ${createObjectiveResponse.code}")
return
}
val createdObjective = JSONObject(createObjectiveResponse.body!!.string())
val objectiveId = createdObjective.getString("id")
println("Created Objective '${createdObjective.getString("name")}' (ID: $objectiveId)")
2. Adding Key Results (Child Objectives)
With the main objective in place, we’ll add a couple of Key Results. In Peoplelogic, key results are objectives that have a parentId linking them to a top-level objective.
// 2. Create Key Results (child objectives)
val keyResults = listOf("Achieve $1M in new revenue", "Improve conversion rate to 5%")
val keyResultIds = mutableListOf<String>()
for (krName in keyResults) {
val krJson = JSONObject(mapOf(
"name" to krName,
"parentId" to objectiveId
))
val createKrRequest = Request.Builder()
.url("$BASE_URL/objective")
.header("Authorization", "Bearer $API_KEY")
.post(RequestBody.create(JSONMedia, krJson.toString()))
.build()
val createKrResponse = client.newCall(createKrRequest).execute()
if (!createKrResponse.isSuccessful) {
println("Failed to create key result: HTTP ${createKrResponse.code}")
return
}
val createdKr = JSONObject(createKrResponse.body!!.string())
val krId = createdKr.getString("id")
keyResultIds.add(krId)
println("Created Key Result '${createdKr.getString("name")}' (ID: $krId)")
}
3. Adding Tasks to the OKRs
Next, we’ll add tasks to each key result via the POST /entity/{entityId}/task endpoint.
// 3. Add Tasks (action items)
val tasks = listOf(
"Identify 50 new sales leads" to "2025-09-30",
"Launch marketing campaign" to "2025-09-30"
)
for ((index, krId) in keyResultIds.withIndex()) {
val (taskName, dueDate) = tasks[index]
val taskJson = JSONObject(mapOf(
"name" to taskName,
"dueDate" to dueDate
))
val createTaskRequest = Request.Builder()
.url("$BASE_URL/entity/$krId/task")
.header("Authorization", "Bearer $API_KEY")
.post(RequestBody.create(JSONMedia, taskJson.toString()))
.build()
val createTaskResponse = client.newCall(createTaskRequest).execute()
if (!createTaskResponse.isSuccessful) {
println("Failed to create task: HTTP ${createTaskResponse.code}")
return
}
val updatedObjective = JSONObject(createTaskResponse.body!!.string())
val task = updatedObjective.getJSONArray("tasks").getJSONObject(0)
println("Added Task '${task.getString("name")}' to Key Result (ID: $krId)")
}
4. Fetching and Displaying the OKR Dashboard
Finally, retrieve all objectives with child objectives and tasks via:
GET /objective?projections=OBJECTIVE_TREE,TASKS
Then print a neat CLI dashboard:
val getOkrsRequest = Request.Builder()
.url("$BASE_URL/objective?projections=OBJECTIVE_TREE,TASKS")
.header("Authorization", "Bearer $API_KEY")
.get()
.build()
val getOkrsResponse = client.newCall(getOkrsRequest).execute()
...
val okrsArray = JSONArray(getOkrsResponse.body!!.string())
println("\nOKR Dashboard:")
for (i in 0 until okrsArray.length()) {
val obj = okrsArray.getJSONObject(i)
printObjective(obj, 0)
}
// Helper to print objectives and tasks recursively
fun printObjective(obj: JSONObject, indent: Int) {
val indentStr = " ".repeat(indent * 2)
val label = if (obj.isNull("parentId")) "Objective" else "Key Result"
println("$indentStr$label: ${obj.getString("name")}")
if (obj.has("tasks")) {
val tasks = obj.getJSONArray("tasks")
for (j in 0 until tasks.length()) {
val task = tasks.getJSONObject(j)
val status = task.getJSONObject("status").getString("current")
val checkbox = if (status == "ACTIVE") "[ ]" else "[x]"
val due = if (task.isNull("dueDate")) "" else " (due ${task.getString("dueDate")})"
println("$indentStr $checkbox ${task.getString("name")}$due")
}
}
if (obj.has("childObjectives")) {
val children = obj.getJSONArray("childObjectives")
for (k in 0 until children.length()) {
printObjective(children.getJSONObject(k), indent + 1)
}
}
}
Expected Console Output:
OKR Dashboard:
Objective: Increase Q3 Sales by 20%
Key Result: Achieve $1M in new revenue
[ ] Identify 50 new sales leads (due 2025-09-30)
Key Result: Improve conversion rate to 5%
[ ] Launch marketing campaign (due 2025-09-30)
That’s it! You’ve built a full CLI dashboard for OKRs using only the Peoplelogic.dev REST API and basic Kotlin libraries.