API Developer BerKarya
Untuk sistem lain yang membaca atau menulis data kepegawaian sebuah perusahaan di BerKarya: ERP, aplikasi kasir, sistem properti, akuntansi, atau dasbor internal.
Basis URL: https://berkarya.app/api/partner/v1
1. Mulai cepat
- HR perusahaan membuka
/admin/integrasidi BerKarya dan menerbitkan kunci dengan scope yang dibutuhkan. Belum punya perusahaan? Daftarkan di sini. - Simpan kuncinya di server Anda. Kunci hanya ditampilkan sekali.
- Panggil
/pinguntuk memastikan kunci, perusahaan, dan izinnya.
curl -s https://berkarya.app/api/partner/v1/ping \
-H "Authorization: Bearer $BERKARYA_KEY"⚠️ Kunci API adalah rahasia server. Jangan menaruhnya di aplikasi seluler, kode peramban, atau variabel lingkungan yang ikut terkirim ke klien.
2. Kunci dan scope
Authorization: Bearer bk_live_<12 karakter>_<32 karakter>Server hanya menyimpan sidik SHA-256 kunci. Kunci hilang? Terbitkan yang baru lalu cabut yang lama. Kunci dicabut, tidak dihapus, supaya jejak audit tetap menunjuk ke pemiliknya. Izin sebuah kunci bisa diubah HR kapan saja tanpa mengganti kuncinya; perubahan (termasuk pencabutan izin) berlaku di permintaan berikutnya.
Scope tidak punya wildcard dan tidak punya hierarki: employees:write tidak memberi employees:read. Kunci hanya bisa apa yang tertulis.
| Scope | Artinya |
|---|---|
org:read | Membaca data organisasi: kantor, jabatan, shift, dan hari libur. |
employees:read | Membaca daftar karyawan beserta jabatan, kantor, dan statusnya. |
employees:write | Membuat dan memperbarui data karyawan. Tidak bisa menghapus. |
attendance:read | Membaca catatan kehadiran: jam masuk, jam pulang, status, dan tanda peninjauan. |
roster:read | Membaca jadwal shift yang sudah ditetapkan. |
requests:read | Membaca pengajuan cuti, sakit, izin, lembur, dan reimburse beserta keputusannya. |
payroll:read | Membaca slip gaji yang SUDAH diterbitkan. Draf tidak pernah ikut. |
points:read | Membaca poin karyawan dan aturannya. |
points:write | Memberi poin kepada karyawan. Dipakai modul aktivasi seperti Berkomunitas. |
webhooks:manage | Mendaftarkan dan mencabut alamat webhook milik perusahaan ini. |
companies:admin | Membuat perusahaan baru dan bertindak atas perusahaan mana pun. HANYA untuk kunci platform.(kunci platform) |
Kunci platform (tanpa perusahaan, diterbitkan oleh pengelola BerKarya) wajib menyebut perusahaan tujuan di setiap permintaan per-perusahaan lewat header X-BerKarya-Company: <id atau slug>. Tanpa header itu permintaannya ditolak, bukan dijawab dengan gabungan data semua perusahaan.
3. Bentuk respons
// sukses berhalaman
{ "ok": true, "data": [ … ], "nextCursor": "clx…" }
// galat
{ "ok": false, "error": "kalimat untuk manusia", "code": "SCOPE_DENIED" }Bercabanglah pada code, bukan pada error. Kalimatnya boleh berubah kapan saja tanpa dianggap perubahan kontrak.
| code | HTTP | Artinya |
|---|---|---|
UNAUTHORIZED | 401 | Header Authorization tidak ada atau bukan Bearer. |
KEY_INVALID | 401 | Kunci salah, dicabut, atau kedaluwarsa. |
SCOPE_DENIED | 403 | Kunci sah, tapi izinnya kurang. Field butuh menyebut scope yang diperlukan. |
COMPANY_REQUIRED | 400 | Kunci platform tanpa header X-BerKarya-Company. |
RATE_LIMITED | 429 | Melewati batas laju. Ada retryAfterSec dan header Retry-After. |
VALIDATION | 400 | Bentuk permintaan salah. |
NOT_FOUND | 404 | Tidak ada, atau milik perusahaan lain (sengaja tidak dibedakan). |
DUPLICATE | 409 | Sudah ada dan tidak boleh dibuat dua kali. |
CONFLICT | 409 | Keadaan sumber daya menolak aksi ini. |
SERVER_ERROR | 500 | Kesalahan di sisi kami. |
- Paginasi:
?limit=(bawaan 50, maksimum 200) dan?cursor=. Berhenti saatnextCursorbernilainull, bukan saat jumlah data lebih kecil dari limit. - Uang (
gross,deductions,net,amount) dikirim sebagai string desimal, misalnya"1250000.00". Jangan parse sebagai float. - Tanggal kerja berbentuk
YYYY-MM-DDtanpa zona. Waktu kejadian berbentuk ISO-8601 UTC. - Sinkronisasi bertahap: endpoint yang menerima
updatedSincehanya mengirim baris yang berubah sejak waktu itu.
4. Endpoint
| Metode | Jalur | Scope | Kegunaan |
|---|---|---|---|
| GET | /ping | — | Identitas kunci: perusahaan mana, izin apa. |
| GET | /org | org:read | Kantor, jabatan, shift, dan hari libur sekaligus. |
| GET | /employees | employees:read | Daftar karyawan, berhalaman. |
| POST | /employees | employees:write | Buat atau perbarui karyawan (upsert lewat externalId). |
| GET | /employees/{id} | employees:read | Satu karyawan. {id} boleh id BerKarya atau externalId Anda. |
| GET | /attendance | attendance:read | Catatan kehadiran per tanggal kerja. |
| GET | /roster | roster:read | Jadwal shift yang sudah ditetapkan. |
| GET | /requests | requests:read | Pengajuan cuti, sakit, izin, lembur, reimburse beserta keputusannya. |
| GET | /payroll | payroll:read | Slip gaji yang SUDAH diterbitkan. Draf tidak pernah ikut. |
| GET | /points | points:read | Poin karyawan. |
| POST | /points | points:write | Beri poin kepada karyawan (idempoten). |
| GET | /webhooks | webhooks:manage | Daftar alamat webhook milik perusahaan ini. |
| POST | /webhooks | webhooks:manage | Daftarkan alamat webhook. Rahasia penanda tangan ditampilkan sekali. |
| PATCH | /webhooks/{id} | webhooks:manage | Nyalakan/matikan alamat atau ubah langganan event. |
| DELETE | /webhooks/{id} | webhooks:manage | Cabut alamat webhook. |
| GET | /companies | companies:admin | Daftar perusahaan. Kunci platform saja. |
| POST | /companies | companies:admin | Buat perusahaan baru. Kunci platform saja. |
GET/ping
Identitas kunci: perusahaan mana, izin apa.
Scope: tidak ada
Panggil ini pertama kali. Membedakan "kunci salah" (401) dari "izin kurang" (403) sebelum menebak-nebak.
{
"ok": true,
"key": { "id": "clk…", "name": "Livioo produksi", "platform": false },
"scopes": ["org:read", "employees:read", "attendance:read"],
"company": { "id": "clc…", "name": "PT Contoh", "slug": "pt-contoh", "timezone": "Asia/Jakarta" },
"serverTime": "2026-10-03T01:00:00.000Z"
}GET/org
Kantor, jabatan, shift, dan hari libur sekaligus.
Scope: org:read
GET/employees
Daftar karyawan, berhalaman.
Scope: employees:read · Parameter: status, updatedSince, limit, cursor
{
"ok": true,
"data": [
{
"id": "cle…",
"externalId": "LIV-882",
"nip": "0012",
"name": "Siti Rahma",
"email": null,
"phone": "+6281234567890",
"area": null,
"status": "active",
"joinDate": "2026-03-01",
"managerId": null,
"position": { "id": "clp…", "name": "Front Office" },
"office": { "id": "clo…", "name": "Kantor Pusat" },
"createdAt": "2026-03-01T02:00:00.000Z",
"updatedAt": "2026-09-20T08:12:00.000Z"
}
],
"nextCursor": null
}POST/employees
Buat atau perbarui karyawan (upsert lewat externalId).
Scope: employees:write
Dijalankan dua kali menghasilkan satu karyawan. Field yang tidak dikirim tidak disentuh; null eksplisit mengosongkan.
curl -s -X POST https://berkarya.app/api/partner/v1/employees \
-H "Authorization: Bearer $BERKARYA_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "LIV-882",
"name": "Siti Rahma",
"phone": "+6281234567890",
"position": "Front Office",
"joinDate": "2026-03-01"
}'GET/employees/{id}
Satu karyawan. {id} boleh id BerKarya atau externalId Anda.
Scope: employees:read
GET/attendance
Catatan kehadiran per tanggal kerja.
Scope: attendance:read · Parameter: from, to, employeeId, updatedSince, limit, cursor
Koordinat mentah dan foto tidak dikirim: yang ada jarak ke kantor dan hasSelfie.
{
"ok": true,
"data": [
{
"id": "cla…",
"externalId": null,
"employeeId": "cle…",
"date": "2026-10-03",
"checkIn": {
"at": "2026-10-03T01:04:00.000Z",
"status": "LATE",
"lateMin": 4,
"officeId": "clo…",
"distanceM": 12,
"accuracyM": 8,
"hasSelfie": true,
"mocked": false
},
"checkOut": null,
"workedMin": null,
"note": null,
"flags": [],
"reviewState": "AUTO_OK",
"reviewedAt": null,
"reviewNote": null,
"updatedAt": "2026-10-03T01:04:00.000Z"
}
],
"nextCursor": null
}GET/roster
Jadwal shift yang sudah ditetapkan.
Scope: roster:read · Parameter: from (wajib), to (wajib), employeeId
GET/requests
Pengajuan cuti, sakit, izin, lembur, reimburse beserta keputusannya.
Scope: requests:read · Parameter: status, type, employeeId, updatedSince, limit, cursor
GET/payroll
Slip gaji yang SUDAH diterbitkan. Draf tidak pernah ikut.
Scope: payroll:read · Parameter: period (YYYY-MM), employeeId
gross, deductions, net dikirim sebagai string desimal.
GET/points
Poin karyawan.
Scope: points:read · Parameter: employeeId
POST/points
Beri poin kepada karyawan (idempoten).
Scope: points:write
Kirim ulang dengan idempotencyKey yang sama: poin tidak ditambah dua kali, balasannya baris yang sama dengan "duplicate": true.
curl -s -X POST https://berkarya.app/api/partner/v1/points \
-H "Authorization: Bearer $BERKARYA_KEY" \
-H "Content-Type: application/json" \
-d '{
"employee": "LIV-882",
"points": 25,
"reason": "Menyelesaikan misi Berkomunitas",
"idempotencyKey": "berkomunitas-misi-9912"
}'GET/webhooks
Daftar alamat webhook milik perusahaan ini.
Scope: webhooks:manage
POST/webhooks
Daftarkan alamat webhook. Rahasia penanda tangan ditampilkan sekali.
Scope: webhooks:manage · Parameter: url (https), events (kosong = semua, termasuk event baru), description
Hanya https. Alamat lokal dan jaringan privat ditolak. Simpan secret dari balasan; tidak bisa dibaca lagi.
curl -s -X POST https://berkarya.app/api/partner/v1/webhooks \
-H "Authorization: Bearer $BERKARYA_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://contoh.id/hooks/berkarya",
"events": ["attendance.punched", "request.decided"],
"description": "Sinkron absensi ke ERP"
}'PATCH/webhooks/{id}
Nyalakan/matikan alamat atau ubah langganan event.
Scope: webhooks:manage · Parameter: active, events, description
DELETE/webhooks/{id}
Cabut alamat webhook.
Scope: webhooks:manage
GET/companies
Daftar perusahaan. Kunci platform saja.
Scope: companies:admin
POST/companies
Buat perusahaan baru. Kunci platform saja.
Scope: companies:admin
5. Data yang sengaja tidak dikirim
- Koordinat mentah absen. Yang dikirim jarak ke kantor.
- Foto selfie. Hanya
hasSelfie: true|false. - Lampiran pengajuan. Isinya surat dokter dan nota pribadi.
- Slip gaji berstatus draf. Belum final dan belum dilihat karyawannya.
Butuh salah satunya untuk alasan yang sah? Ajukan scope baru. Bentuk yang ada tidak akan dilebarkan diam-diam.
6. Webhook
Daftarkan alamat https lewat POST /webhooks atau di /admin/integrasi, lalu BerKarya menghubunginya saat sesuatu terjadi.
| Event | Kapan |
|---|---|
attendance.punched | Karyawan absen masuk atau pulang. Memuat status, tanda peninjauan, dan jarak ke kantor. |
attendance.reviewed | Atasan mengesahkan atau menolak absen yang ditandai. |
request.submitted | Pengajuan baru masuk (cuti, sakit, izin, lembur, reimburse). |
request.decided | Pengajuan disetujui atau ditolak. |
payroll.published | Slip gaji satu periode diterbitkan ke karyawan. |
employee.created | Karyawan baru dibuat. |
employee.updated | Data karyawan berubah. |
points.granted | Poin diberikan kepada karyawan. |
POST https://alamat-anda/…
X-BerKarya-Event: attendance.punched
X-BerKarya-Delivery: wd_…
X-BerKarya-Signature: t=1758503011,v1=9f2a…
{
"id": "wd_…",
"event": "attendance.punched",
"createdAt": "2026-09-22T01:03:11.000Z",
"data": { … }
}v1 adalah HMAC-SHA256 dari "<t>.<badan mentah>" dengan rahasia alamat Anda. Contoh verifikasi (Node.js):
import { createHmac, timingSafeEqual } from "node:crypto";
export function sah(secret, badanMentah, header, toleransiDetik = 300) {
const bagian = Object.fromEntries(
header.split(",").map((p) => p.split("=").map((x) => x.trim())),
);
const t = Number(bagian.t);
if (!Number.isFinite(t)) return false;
if (Math.abs(Date.now() / 1000 - t) > toleransiDetik) return false;
const harap = createHmac("sha256", secret).update(`${t}.${badanMentah}`).digest();
const dapat = Buffer.from(bagian.v1, "hex");
return harap.length === dapat.length && timingSafeEqual(harap, dapat);
}- Pakai badan mentah, bukan hasil parse lalu stringify ulang.
- Periksa stempel waktu supaya muatan lama tidak bisa diputar ulang.
- Bandingkan dengan
timingSafeEqual, bukan===. - Balas 2xx dalam 10 detik. Gagal akan dicoba lagi setelah 1, 5, 30, 120, dan 360 menit. Pengiriman bersifat setidaknya sekali, jadi penerima wajib idempoten terhadap
id.
7. Batas laju dan jejak
Batas laju dihitung per kunci per menit (bawaan 120, diatur saat menerbitkan). Lewat batas dijawab 429 RATE_LIMITED beserta retryAfterSec.
Setiap penulisan lewat kunci tercatat di jejak audit perusahaan dengan nama kuncinya, sehingga HR selalu bisa melihat sistem mana yang mengubah apa.
Pertanyaan integrasi atau permintaan scope baru: wiro@drwcorp.com.
