P
JKT48Connect
๐Ÿ”’ Mode admin aktif โ€” bagian registrasi & klaim akses ditampilkan.
Dokumentasi Partner

Akses pesan pribadi
member JKT48, secara aman.

PM Partner API memberi aplikasi kamu akses baca ke pesan yang dibagikan member JKT48 lewat JKT48Connect โ€” dengan identitas partner sendiri, kontrol akses per-member, dan token yang cuma bisa dipakai oleh perangkat yang menerbitkannya.

01 Overview

Semua permintaan partner masuk lewat dua base URL berikut. Simpan keduanya โ€” kamu akan memakainya di setiap request.

LayananBase URLKegunaan
Gateway https://v5.jkt48connect.com/api/pm Registrasi, klaim akses, generate token
Messages https://pmv.jkt48connect.com Ambil isi pesan member
Alur singkat

Daftar sekali โ†’ klaim akses ke member yang kamu perlukan โ†’ generate token setiap mau ambil data โ†’ panggil endpoint messages dengan token itu. Token berumur pendek, jadi langkah generate perlu diulang secara berkala.

02 Konsep dasar

Empat istilah yang akan sering kamu temui di dokumentasi ini.

IstilahArtinyaDidapat dari
kidID unik partner kamu, didapat sekali saat registrasi.Tim JKT48Connect
secretKunci rahasia untuk menandatangani request ke Gateway.Tim JKT48Connect
apikeyKunci statis yang dipakai di parameter ?apikey= saat generate token.Tim JKT48Connect
DPoP keySepasang kunci yang kamu buat sendiri untuk membuktikan setiap permintaan ke Messages API benar dari perangkatmu.Kamu generate sendiri
tokenIzin baca sementara ke satu member tertentu, dikeluarkan lewat /generate.Otomatis, hasil generate
Aturan sederhananya

Kalau nilainya unik untuk identitas partner kamu (kid, secret, apikey) โ€” itu dikeluarkan oleh tim JKT48Connect lewat proses admin, kamu nggak bisa bikin sendiri.

Kalau nilainya berupa key pair kriptografi punya perangkatmu (DPoP private/public key) โ€” itu kamu generate dan simpan sendiri, tidak pernah diminta dari admin.

03 Quickstart

Dari nol sampai membaca pesan pertama, dalam empat langkah.

Buat DPoP key pair Kamu generate sendiri

Generate keypair ECDSA P-256 di sisi kamu sendiri. Private key tidak pernah dikirim ke mana pun โ€” bukan sesuatu yang diminta atau dikeluarkan oleh admin.

Node.js
const { generateKeyPairSync } = require("crypto");
const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "P-256" });

// simpan ini di tempat aman, JANGAN pernah dibagikan
console.log(privateKey.export({ type: "pkcs8", format: "pem" }));

// kirim ini saat registrasi
console.log(JSON.stringify(publicKey.export({ format: "jwk" })));

Daftar sebagai partner ADMIN

Kirim public key kamu ke /register. Simpan kid dan secret yang dikembalikan โ€” keduanya hanya muncul sekali. (Langkah ini dilakukan oleh tim JKT48Connect.)

Klaim akses ke member ADMIN

Aktifkan akses baca untuk member yang kamu perlukan lewat /access/claim. Berlaku 30 hari, tinggal klaim ulang untuk perpanjang. (Langkah ini dilakukan oleh tim JKT48Connect.)

Generate token & ambil pesan

Tiap kali mau ambil data, generate token baru lewat /generate, lalu pakai untuk memanggil /messages/:identifier di Messages API. Langkah ini butuh kid, secret, dan apikey yang kamu simpan dari proses admin sebelumnya Dari tim JKT48Connect โ€” kamu sendiri hanya perlu memakainya, tidak perlu meminta ulang tiap kali.

04 Registrasi partner ADMIN

Registrasi butuh API key yang diberikan oleh tim JKT48Connect. Hubungi tim kami kalau belum punya.

POST v5.jkt48connect.com/api/pm/register?key=API_KEY_KAMU
Request body
{
  "label": "Nama Aplikasi Kamu",
  "dpop_public_jwk": {
    "kty": "EC",
    "crv": "P-256",
    "x": "...",
    "y": "..."
  }
}
Response
{
  "status": true,
  "data": {
    "kid": "p_xxxxxxxxxxxx",
    "secret": "..."
  }
}
Simpan sekarang juga

secret hanya ditampilkan sekali di response ini. Kalau hilang, kamu perlu melakukan rotate secret untuk mendapat yang baru.

05 Membuat DPoP key

DPoP key adalah keypair ECDSA P-256 milikmu sendiri โ€” bukan diberikan oleh JKT48Connect. Private key-nya tetap di perangkat kamu selamanya; yang dikirim ke kami hanya public key-nya (saat registrasi atau saat rotate).

Siapa yang bikin apa

Kamu generate sendiri โ€” private & public key DPoP dibuat sepenuhnya di sisi kamu, kapan saja, tanpa perlu izin atau bantuan admin.

Yang perlu koordinasi dengan admin hanyalah mengirim public key-nya saat registrasi awal (bagian Registrasi partner) atau saat rotate.

Kapan harus rotate

Kalau kamu mengganti server, migrasi environment, atau curiga private key pernah ter-expose (misal ke-commit ke repo publik), segera rotate lewat /partner/rotate-key.

06 Menandatangani request

Tiga endpoint di Gateway (/access/claim, /access, /generate) mewajibkan tiga header berikut di setiap panggilan.

HeaderIsi
x-kidkid partner kamu
x-timestampUnix timestamp (milidetik) saat request dibuat
x-signatureTanda tangan HMAC-SHA256 dari request ini
Contoh implementasi (Node.js)
import crypto from "crypto";

function signRequest(secret, method, path, extraSalt = "") {
  const timestamp = String(Date.now());
  const derivedKey = crypto.hkdfSync(
    "sha256",
    Buffer.from(secret, "hex"),
    Buffer.from(timestamp, "utf8"),
    Buffer.from(`pm-gateway:${path}`, "utf8"),
    32
  );
  const signingStr = `${method}:${path}:${KID}:${timestamp}${extraSalt}`;
  const signature = crypto
    .createHmac("sha256", Buffer.from(derivedKey))
    .update(signingStr)
    .digest("hex");

  return { timestamp, signature };
}
Wajib: path pakai full path, extraSalt wajib diisi untuk endpoint tertentu

path yang dipakai untuk signing harus full path termasuk prefix, misalnya /api/pm/generate โ€” bukan /generate saja.

Untuk endpoint /access/claim dan /generate, parameter extraSalt wajib diisi dengan titik dua diikuti nilai identifier member, contoh:

const { timestamp, signature } = signRequest(secret, "POST", "/api/pm/generate", `:${identifier}`);
// signingStr akan jadi: POST:/api/pm/generate:KID:TIMESTAMP:Lily

Tanpa extraSalt ini, server akan menolak signature dengan pesan "Signature tidak valid atau sudah dipakai (replay)" meskipun kid, secret, dan timestamp semuanya benar โ€” errornya generic dan tidak menyebutkan extraSalt secara eksplisit, jadi mudah membingungkan.

Endpoint /access (GET, melihat akses aktif) tidak butuh extraSalt karena tidak menyasar identifier tertentu.

Jendela waktu

x-timestamp hanya diterima dalam rentang ยฑ60 detik dari waktu server. Pastikan jam sistem kamu tersinkron (NTP) supaya tidak mendapat error timestamp.

Ingat asal kredensialnya

KID dan SECRET yang dipakai buat menandatangani request ini didapat dari tim JKT48Connect saat proses registrasi โ€” bukan nilai yang kamu buat sendiri. Kalau belum punya, hubungi tim kami dulu.

07 Klaim akses ke member ADMIN

Aktifkan akses baca ke satu member JKT48 sebelum bisa generate token untuknya. Panggil ulang endpoint ini kapan saja untuk memperpanjang masa aktifnya.

POST v5.jkt48connect.com/api/pm/access/claim
Headers
x-kid: p_xxxxxxxxxxxx
x-timestamp: 1751870000000
x-signature: ...
Content-Type: application/json
Body
{ "identifier": "Lily" }
Response
{
  "status": true,
  "message": "Akses ke \"Lily\" aktif selama 30 hari.",
  "data": {
    "identifier": "Lily",
    "expires_at": 1754462000
  }
}
Jangan lupa extraSalt

Signature untuk endpoint ini wajib disign dengan extraSalt = `:${identifier}`, sama seperti pada /generate. Lihat contoh lengkap di bagian 06.

Melihat akses aktif

GET v5.jkt48connect.com/api/pm/access

Pakai header signing yang sama untuk melihat daftar member yang sedang aktif untuk akun kamu, beserta tanggal kedaluwarsanya. Endpoint ini tidak butuh extraSalt.

08 Generate token

Token berumur pendek dan hanya berlaku untuk satu member. Generate token baru setiap kali kamu akan mengambil pesan โ€” jangan simpan token untuk dipakai berulang dalam jangka panjang.

POST v5.jkt48connect.com/api/pm/generate?apikey=API_KEY_KAMU
Headers
x-kid: p_xxxxxxxxxxxx
x-timestamp: 1751870005000
x-signature: ...
x-identifier: Lily
extraSalt wajib diisi

Sama seperti /access/claim, signature endpoint ini wajib memakai extraSalt = `:${identifier}`. Server akan merespons { status: false, message: "Signature tidak valid atau sudah dipakai (replay)" } kalau extraSalt ini tidak disertakan, meskipun kredensial lain sudah benar.

Ingat asal kredensialnya

Parameter ?apikey= di URL ini didapat dari tim JKT48Connect, sama seperti kid dan secret โ€” bukan sesuatu yang kamu generate sendiri.

Response
{
  "status": true,
  "data": {
    "token": "eyJhbGciOi...",
    "expiresIn": 600,
    "identifier": "Lily"
  }
}

09 Mengambil pesan

Endpoint ini ada di domain terpisah, pmv.jkt48connect.com, dan mewajibkan dua header: token yang baru kamu generate, dan sebuah DPoP proof โ€” bukti bahwa request ini benar berasal dari perangkat yang memegang DPoP key kamu.

GET pmv.jkt48connect.com/messages/:identifier?page=1

Membuat DPoP proof

DPoP proof adalah JWT pendek yang kamu tanda tangani sendiri pakai DPoP private key, dibuat baru untuk setiap request.

import crypto from "crypto";

function b64url(buf) {
  return buf.toString("base64")
    .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

function makeDpopProof(privateKeyPem, method, url) {
  const header = { alg: "ES256", typ: "dpop+jwt" };
  const payload = {
    htm: method,
    htu: url,
    iat: Math.floor(Date.now() / 1000),
    jti: crypto.randomUUID(),
  };
  const h = b64url(Buffer.from(JSON.stringify(header)));
  const p = b64url(Buffer.from(JSON.stringify(payload)));

  const signer = crypto.createSign("SHA256");
  signer.update(`${h}.${p}`);
  signer.end();
  const sig = signer.sign({ key: privateKeyPem, dsaEncoding: "ieee-p1363" });

  return `${h}.${p}.${b64url(sig)}`;
}

Memanggil endpoint

const urlNoQuery = `https://pmv.jkt48connect.com/messages/Lily`;
const urlWithQuery = `${urlNoQuery}?page=1`;
const dpop = makeDpopProof(privateKeyPem, "GET", urlNoQuery);

const res = await fetch(urlWithQuery, {
  headers: {
    Authorization: `Bearer ${token}`,
    DPoP: dpop,
  },
});
const data = await res.json();
DPoP proof di-sign tanpa query string

Saat membuat DPoP proof, field htu harus berisi URL tanpa query string (misalnya https://pmv.jkt48connect.com/messages/Lily, bukan ...?page=1). Request fetch yang sebenarnya tetap dikirim ke URL lengkap dengan ?page=1, tapi proof-nya sendiri disign dari base path saja.

Response
{
  "status": true,
  "idol_name": "Lily",
  "page": 1,
  "encrypted": true,
  "data": "base64-ciphertext..."
}
Kenapa field data terenkripsi?

Isi pesan dienkripsi khusus untuk akun kamu. Lihat bagian dekripsi untuk cara membacanya.

10 Mengganti kredensial

Dua hal yang bisa kamu rotate sendiri, kapan pun, tanpa menunggu tim kami.

Rotate secret (HMAC)

POST v5.jkt48connect.com/api/pm/partner/rotate-secret

Tanda tangani request ini dengan secret lama. Secret baru langsung aktif; token yang sudah terlanjur ada tetap valid sampai kedaluwarsa.

Rotate DPoP key

POST v5.jkt48connect.com/api/pm/partner/rotate-key
Body
{ "dpop_public_jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." } }

Setelah rotate, generate token baru sebelum memanggil /messages lagi โ€” token yang diterbitkan sebelum rotate tidak akan lagi cocok dengan key baru.

11 Membaca response terenkripsi

Field data dari /messages berisi base64 dari iv (12 byte) + authTag (16 byte) + ciphertext, dienkripsi AES-256-GCM memakai output secret yang diberikan tim kami saat onboarding.

import crypto from "crypto";

function decryptMessageData(encryptedBase64, outputSecret) {
  const key = crypto.createHash("sha256").update(outputSecret).digest();
  const combined = Buffer.from(encryptedBase64, "base64");

  const iv = combined.subarray(0, 12);
  const authTag = combined.subarray(12, 28);
  const ciphertext = combined.subarray(28);

  const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
  decipher.setAuthTag(authTag);

  const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
  return JSON.parse(decrypted.toString("utf8"));
}

Belum punya output secret kamu? Hubungi tim JKT48Connect saat onboarding untuk mendapatkannya.

12 Kode error

Semua endpoint mengembalikan bentuk yang sama: { status: false, message: "..." }.

StatusArtiYang perlu dilakukan
400 Header wajib hilang Pastikan x-kid, x-timestamp, x-signature terkirim
401 Timestamp di luar jendela waktu Sinkronkan jam sistem (NTP)
401 Signature tidak valid Cek ulang cara penandatanganan di bagian 06 โ€” penyebab paling umum: lupa menyertakan extraSalt (:identifier) untuk endpoint /access/claim dan /generate, atau path yang disign bukan full path (/api/pm/...)
401 DPoP proof invalid Pastikan proof dibuat baru per request dan URL-nya (tanpa query string) sama persis dengan path yang dipanggil
401 Token binding tidak cocok DPoP key baru saja dirotasi โ€” generate token baru
403 Belum punya akses ke member ini Klaim akses dulu lewat /access/claim (ingat: sertakan extraSalt :identifier saat signing)
429 Terlalu banyak permintaan Kurangi frekuensi request, coba lagi sebentar lagi
500 Gangguan di sisi kami Coba lagi; hubungi tim kami kalau berulang

13 Rate limit

Setiap partner memiliki kuota permintaan wajar untuk penggunaan normal. Kalau aplikasi kamu perlu volume lebih tinggi dari kuota default, hubungi tim JKT48Connect untuk penyesuaian.

Praktik baik

Cache hasil /messages di sisi kamu selama beberapa detik alih-alih polling terus-menerus, dan generate token hanya saat benar-benar akan dipakai.

14 FAQ

Kenapa harus ada dua langkah, secret dan DPoP key?

Secret membuktikan identitas akun kamu ke Gateway. DPoP key membuktikan bahwa permintaan ke Messages API benar berasal dari perangkat kamu sendiri โ€” jadi token tetap aman dipakai meskipun secret dan DPoP key kamu simpan terpisah.

Berapa lama token berlaku?

10 menit. Generate token baru setiap kali kamu akan mengambil data terbaru.

Bagaimana kalau DPoP private key saya bocor?

Segera panggil /partner/rotate-key dengan public key baru. Key lama langsung tidak berlaku setelah itu.

Bisa akses banyak member sekaligus?

Bisa. Klaim akses satu per satu lewat /access/claim, lalu generate token terpisah untuk masing-masing member saat dibutuhkan.

Kenapa muncul "Signature tidak valid atau sudah dipakai (replay)" padahal kid, secret, dan jam sudah benar?

Penyebab paling umum: lupa menyertakan extraSalt (:identifier) saat signing untuk endpoint /access/claim dan /generate, atau path yang disign tidak memakai full path (/api/pm/...). Lihat contoh lengkap di bagian 06.

Butuh bantuan lebih lanjut?

Hubungi tim JKT48Connect melalui kanal partner yang sudah kamu gunakan saat onboarding.

โ†‘ Kembali ke atas