Harness API
Endpoint yang dipanggil langsung oleh pipeline build. Semuanya ada di bawah /api/v1/harness dan menjawab JSON.
Autentikasi
Permintaan membawa token harness sebagai bearer token:
Authorization: Bearer <token>
Accept: application/json
Token harness diterbitkan orang yang punya akses harness: administrator arcade atau developer yang sudah disetujui , dan bisa dicabut kapan saja. Ada dua cara mendapatkannya:
- Tautan perangkat: alur yang dipakai
taploka-mcp login, dijelaskan di bawah. Tidak ada yang disalin manual. - Konsol: administrator atau panel developer sendiri membuat kunci bernama lalu menyerahkannya.
Perlakukan token seperti kata sandi: dia bisa menerbitkan ke katalog. Simpan di penyimpanan rahasia, jangan pernah di repositori.
Tautan perangkat
Handshake tiga langkah yang memasang kunci ke sebuah mesin tanpa ada yang perlu menempel-nempel teks.
1. Minta kode
POST /api/v1/harness/device
Content-Type: application/json
{ "name": "build-box" }
{
"device_code": "…",
"user_code": "RT2H-Z46S",
"verification_uri": "https://your-arcade.example.com/admin/link",
"verification_uri_complete": "https://your-arcade.example.com/admin/link?code=RT2H-Z46S",
"interval": 2,
"expires_in": 600
}
2. Seseorang menyetujui user code di konsol. Administrator arcade atau developer pemilik mesin itu bisa , persetujuan mencetak kunci harness atas nama penyetuju.
3. Polling untuk mengambil kuncinya
POST /api/v1/harness/device/token
Content-Type: application/json
{ "device_code": "…" }
Selama belum ada yang mengklik, jawabannya 202 dengan {"status": "pending"}, bukan error. Begitu disetujui,
jawabannya 200 berisi tokennya. Kunci diserahkan tepat sekali; tautan yang menunggu dihancurkan bersamanya, dan
tautan yang tidak disetujui kedaluwarsa setelah sepuluh menit. Kedua endpoint dibatasi rate-nya per IP.
Push sebuah bundle
POST /api/v1/harness/bundles
Content-Type: multipart/form-data
| Kolom | Arti |
|---|---|
bundle |
Berkas ZIP-nya. Wajib. |
game |
Slug game yang sudah ada. Wajib kecuali title diisi. |
title |
Nama untuk push pertama; slug diturunkan dari sini. |
rollout |
0-100, default 100. |
changelog |
Catatan pendek. |
draft |
1 untuk menyimpan tanpa merilis. |
{
"game": "rosok-garage",
"game_created": true,
"version": 1,
"status": "released",
"rollout": 100,
"sdk_min": 1,
"size_bytes": 69414,
"files": 5,
"play_url": "https://your-arcade.example.com/main/rosok-garage",
"checks": [{ "label": "Struktur wajib", "detail": "index.html + game.json ditemukan", "ok": true }]
}
Bundle yang gagal validasi dibalas 422 dengan kode BUNDLE_INVALID dan array checks yang sama, jadi pipeline bisa
mencetak persis apa yang salah.
Baca katalog
GET /api/v1/harness/games
GET /api/v1/harness/games/{game}
games menampilkan slug, judul, status, jumlah versi, versi aktif, dan URL main. games/{game} menambahkan blok
metadata, semua versi lengkap dengan rollout, batas bawah SDK, ukuran, dan changelog-nya, total skor termasuk berapa
yang ditandai untuk ditinjau, serta URL papan peringkat.
Di situ ada juga languages, yaitu kode bahasa yang sedang diiklankan arcade ini, dan cuma kode itulah yang diterima
translations. Baca dari sini, jangan ditulis mati di pipeline: operator bisa menambah bahasa, dan teks yang dikirim
dengan kode di luar daftar akan dibuang.
Perbarui metadata
POST /api/v1/harness/games/{game}
Kirim hanya kolom yang ingin kamu ubah: title, tagline, description, translations, orientation, max_score,
max_score_per_second, cumulative_score, sort_order, categories, features, cover_url, banner_url, coin_price,
screenshots_url, atau berkas cover / banner / screenshots[] sebagai multipart. Responsnya sama bentuknya dengan membaca game, jadi pipeline bisa
menulis dan membaca balik dalam satu panggilan.
screenshots berlaku seperti shop: mengirimnya mengganti seluruh set (sampai 6 gambar). Berkas di-encode ulang jadi
WebP seperti sampul; screenshots_url menerima URL yang sudah di-host, dan null mengosongkan daftarnya.
title, tagline, dan description memakai bahasa rumah arcade, yaitu Indonesia. Kirim teks Indonesianya ke situ dan
taruh yang Inggris di translations.en, jangan dibalik: URL polos yang diindeks mesin pencari adalah versi Indonesia,
jadi teks Inggris di kolom dasar membuat halaman Indonesia terbaca Inggris.
translations berisi teks toko per bahasa, dikunci dengan kode bahasa yang diiklankan arcade (sama dengan yang ada di
tautan hreflang; game_status mengembalikan daftar yang berlaku). Seperti shop, dia mengganti seluruh peta. Hanya
tagline dan description yang diterjemahkan, dan bahasa yang tidak diiklankan arcade dibuang, bukan disimpan:
{
"translations": {
"en": { "tagline": "Cook fast, serve faster.", "description": "Run the stall, chase the record." }
}
}
Kolom yang dikosongkan jatuh ke kolom milik game itu sendiri, jadi halaman Inggris tanpa tagline Inggris tetap menampilkan yang Indonesia, bukan kosong. Judul tidak pernah diterjemahkan: itu nama merek, dan menggantinya per bahasa memecah game jadi dua di hasil pencarian.
coin_price adalah yang membuat game premium: angka membuatnya tetap bisa dilihat di katalog, tapi memulai sesi
butuh pembelian koin yang dikredit ke pemilik game; null mengembalikannya jadi gratis.
max_score menandai satu skor yang melewati batas atas; max_score_per_second menandai skor yang naik terlalu cepat.
Biarkan nilai per-detiknya null dan default global yang dipakai, yang cocok untuk puzzle tapi tidak untuk game idle
yang menyetor jutaan.
cumulative_score untuk game yang menyetor total berjalan, bukan skor per ronde, yaitu hampir semua game idle dan
tycoon. Tanpa itu semua periode menampilkan angka yang sama, karena total terbaik hari ini juga total terbaik sepanjang
waktu. Dengan itu, papan harian dan mingguan memuat perolehan pemain di periode tersebut dan hanya papan sepanjang
waktu yang menyimpan totalnya.
Sampul yang diunggah di-encode ulang jadi WebP dan diskalakan muat 1200x800, jadi cover_url selalu menunjuk berkas
.webp apa pun yang dikirim. Unggahannya sendiri menerima PNG, JPEG, atau WebP sampai 3 MB.
banner adalah gambar lebar (sekitar 1200x500) untuk hero beranda dan header halaman detail, tempat sampul 2:3 tidak
muat. Di-encode ulang dengan cara yang sama. Dibiarkan kosong, kedua slot itu memakai sampul.
Toko koin
shop adalah satu-satunya kolom yang tidak parsial: dia mengganti seluruh daftar harga game itu.
{
"shop": [
{ "sku": "engine_lv2", "title": "Mesin Lv2", "coin_price": 800 },
{ "sku": "fuel", "title": "Bensin", "coin_price": 150, "type": "consumable" }
]
}
Sku yang tidak ada di daftar dinonaktifkan, bukan dihapus, jadi pemain yang sudah membelinya tetap punya nama untuk
ditampilkan. type bernilai non_consumable (default, dimiliki sekali) atau consumable (menumpuk jumlahnya), dan
active memensiunkan sebuah entri di tempat. Membaca balik game mengembalikan shop berisi semua entri, termasuk yang
sudah pensiun.
Harga hanya pernah dibaca dari sini. Lihat Koin dan toko untuk cara bundle membeli terhadap daftar ini.
Error
Kegagalan dijawab dengan bentuk yang tetap:
{ "message": "…", "code": "BUNDLE_INVALID", "data": { "checks": [] } }
| Kode | Arti |
|---|---|
BUNDLE_INVALID |
Ada pemeriksaan validasi yang gagal; data.checks menyebut yang mana |
DEVICE_UNKNOWN |
Device code tidak dikenal, sudah dipakai, atau sudah kedaluwarsa |
DEVICE_EXPIRED |
Tautannya tidak disetujui tepat waktu; ulangi dari awal |
Contoh: terbitkan dari CI
curl -sS -X POST "$TAPLOKA_URL/api/v1/harness/bundles" \
-H "Authorization: Bearer $TAPLOKA_KEY" \
-H "Accept: application/json" \
-F "game=rosok-garage" \
-F "bundle=@build/game.zip" \
-F "rollout=20" \
-F "changelog=$(git log -1 --pretty=%s)"