Performance
Manage OKRs, appraisals, and promotions via the API.
The Performance endpoints cover three areas — objectives (OKRs), appraisals, and promotions — for your own employees.
Call https://api.kuahr.com with the company’s authorised Live key in KUA_API_KEY. The SDKs use this address by default: Node or Python. Replace the illustrative IDs with real records from this company; the public API does not list employees. Persist an operation-specific idempotency key before writes and reuse the exact request on interrupted responses.
Objectives (OKRs)
An objective has key results and progress check-ins.
# Create an objective with a key result
curl "https://api.kuahr.com/v1/performance/objectives" \
-X POST -H "X-Api-Key: kua_live_your_key_here" -H "Content-Type: application/json" \
-d '{
"title": "Grow monthly active users",
"cycleLabel": "2026 Q3",
"cycleStart": "2026-07-01",
"cycleEnd": "2026-09-30",
"keyResults": [{ "description": "Reach 50k MAU", "targetValue": 50000, "unit": "users" }]
}'
# List / get
curl "https://api.kuahr.com/v1/performance/objectives?cycleLabel=2026%20Q3" -H "X-Api-Key: kua_live_your_key_here"const objective = await kua.performance.objectives.create({
title: "Grow monthly active users",
cycleLabel: "2026 Q3",
cycleStart: "2026-07-01",
cycleEnd: "2026-09-30",
keyResults: [{ description: "Reach 50k MAU", targetValue: 50000, unit: "users" }],
});
await kua.performance.objectives.list({ cycleLabel: "2026 Q3" });
await kua.performance.keyResults.update(objective.keyResults[0].id, { currentValue: 12000, confidenceScore: 0.7 });
await kua.performance.checkIns.create(objective.objective.id, { note: "On track", confidence: 0.7 });objective = kua.performance.objectives.create(
title="Grow monthly active users", cycle_label="2026 Q3",
cycle_start="2026-07-01", cycle_end="2026-09-30",
key_results=[{"description": "Reach 50k MAU", "targetValue": 50000, "unit": "users"}],
)
kua.performance.objectives.list(cycle_label="2026 Q3")
kua.performance.key_results.update(objective.key_results[0].id, current_value=12000, confidence_score=0.7)
kua.performance.check_ins.create(objective.objective.id, note="On track", confidence=0.7)Appraisals
Creating a cycle seeds a review for every active employee. Self and manager submissions are separate steps: self submission can move PendingSelf to PendingManager, while manager submission records ReviewsSubmitted and may precede a self submission. Submitted reviews are not a completed/calibrated appraisal; completion is a separate authorised product flow.
curl "https://api.kuahr.com/v1/performance/appraisal-cycles" \
-X POST -H "X-Api-Key: kua_live_your_key_here" -H "Content-Type: application/json" \
-d '{ "name": "H2 2026 review", "opensOn": "2026-10-01", "closesOn": "2026-10-31" }'
curl "https://api.kuahr.com/v1/performance/appraisal-cycles/{id}/reviews" -H "X-Api-Key: kua_live_your_key_here"
curl -X POST "https://api.kuahr.com/v1/performance/reviews/{id}/manager" \
-H "X-Api-Key: kua_live_your_key_here" -H "Content-Type: application/json" \
-d '{ "body": "Great half", "rating": 4 }'const cycle = await kua.performance.appraisalCycles.create({ name: "H2 2026 review", opensOn: "2026-10-01", closesOn: "2026-10-31" });
const reviews = await kua.performance.appraisalCycles.reviews(cycle.id);
const review = reviews.find(item => item.employeeId === intendedEmployeeId);
if (!review) throw new Error("No eligible review for the intended employee");
// Only when each role is authorised and the current review state permits it:
await kua.performance.reviews.submitSelf(review.id, { body: "My self assessment", rating: 4 });
await kua.performance.reviews.submitManager(review.id, { body: "Manager assessment", rating: 4 });cycle = kua.performance.appraisal_cycles.create(name="H2 2026 review", opens_on="2026-10-01", closes_on="2026-10-31")
reviews = kua.performance.appraisal_cycles.reviews(cycle.id)
review = next((row for row in reviews if row.employee_id == intended_employee_id), None)
if review is None:
raise ValueError("No eligible review for the intended employee")
# Each submission needs its intended actor's authority and eligible current state.
kua.performance.reviews.submit_self(review.id, body="My self assessment", rating=4)
kua.performance.reviews.submit_manager(review.id, body="Manager assessment", rating=4)Promotions
An append-only record of role/salary changes (amounts in minor units, e.g. kobo):
curl "https://api.kuahr.com/v1/performance/promotions" \
-X POST -H "X-Api-Key: kua_live_your_key_here" -H "Content-Type: application/json" \
-d '{ "employeeId": "b2c3…", "fromTitle": "Engineer", "toTitle": "Senior Engineer", "toSalaryMinor": 55000000, "effectiveDate": "2026-08-01" }'
curl "https://api.kuahr.com/v1/performance/employees/{employeeId}/promotions" -H "X-Api-Key: kua_live_your_key_here"await kua.performance.promotions.create({ employeeId: "b2c3…", fromTitle: "Engineer", toTitle: "Senior Engineer", toSalaryMinor: 55_000_000, effectiveDate: "2026-08-01" });
await kua.performance.promotions.list("b2c3…");kua.performance.promotions.create(employee_id="b2c3…", from_title="Engineer", to_title="Senior Engineer", to_salary_minor=55_000_000, effective_date="2026-08-01")
kua.performance.promotions.list("b2c3…")Verify each workflow
| Workflow | Ordered actions | Completion and alternatives |
|---|---|---|
| Objectives | Create → retain objective/key-result IDs → update a key result → add a check-in → GET objective detail | Read the detail again and verify progress. An empty list needs a deliberate create, not indexing its first item. |
| Appraisal | Create/list cycle → list reviews → select intended employee → authorised self submission → authorised manager submission | Self submission uses POST /v1/performance/reviews/{id}/self; re-read the cycle’s reviews after each applicable step. No eligible employees can mean no reviews. Do not submit another employee’s review to finish an example. |
| Promotion | Record approved role/compensation change → list that employee’s promotions | Verify the returned record and effective date. Recording a promotion does not update payroll salary or disburse pay; coordinate the separately authorised salary-structure handoff. |
Self and manager submissions are different responsibilities, not permission to impersonate another actor. The snippets illustrate the lifecycle only; use the credential/actor authorised for each step and inspect state before submitting. A failed/duplicate/out-of-order submission requires current-state readback, not a loop through transitions.
The objective detail route is GET /v1/performance/objectives/{id}; key-result progress is PATCH /v1/performance/key-results/{id}. The API reference provides exact request schemas. Use the matching read/write grants, retain write keys and handle missing IDs or empty lists without producing unintended records.
Done when: the intended objective/review/promotion is present with the expected state, and any separate payroll handoff has its own owner and evidence. Continue with go-live readiness.