Docs / REST API

REST API

Everything you can do in the panel is a public API call. The web panel, MCP, and your scripts all use the same API.

Basics

ItemValue
Base URLhttps://app.saka.work/api/v1
AuthenticationAuthorization: Bearer saka_… (create one in Settings, see Create a token)
FormatJSON. Send Content-Type: application/json for requests that change something.
Request fieldsUnknown fields are rejected, so typos are not silently ignored.
LanguageAccept-Language: en or id (the default) for error, progress, and note text. See Response language.
Terminal
export SAKA_TOKEN="saka_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Error format

Every error has the same shape, so it can be read by both people and AI agents. Field names and messages are in Indonesian, because that is what the API returns:

JSON
{
  "galat": {
    "kode": "server-terputus",
    "arti": "Server sedang tidak tersambung ke sakapanel.",
    "langkah": "Pastikan server hidup dan agent berjalan: `systemctl status saka-agent`. Aplikasi di server tetap jalan walau terputus."
  }
}

Common codes: belum-masuk (401), server-tidak-ada (404), server-terputus (409), perlu-konfirmasi (400), batas-server (403), isian-rusak (400), agent-sedang-diperbarui.

Response language

The panel is available in English (the default) and Indonesian, and the API follows the Accept-Language header. With Accept-Language: en, text meant for people (arti, langkah, task progress steps, and notes) is sent in English. Without the header, or with id, responses are in Indonesian. What never changes: the error kode, JSON field names, and values such as keadaan, so your code only needs to match on kode. MCP and Telegram messages stay in Indonesian.

curl
curl -s https://app.saka.work/api/v1/server/<server-id> \
  -H "Authorization: Bearer $SAKA_TOKEN" \
  -H "Accept-Language: en"

Long-running tasks

Actions on a server are sent as tasks. If a task finishes quickly, the response contains the result right away. If not, the response is a 202 with the task and a note on how to follow it. Poll GET /tugas/{id} until keadaan becomes selesai or gagal. Creation of database clusters, load balancers, and sites is tracked through GET /database/{id}, GET /lb/{id}, and GET /situs/{id} (keadaan: dibuat, siap, or gagal).

Main endpoints

All paths below are relative to /api/v1.

GroupMethodPathDetails
AccountGET/akunAccount profile and Telegram status.
GET/tokenList API tokens.
POST/tokenCreate a token. Body {"nama"}. The secret is shown only once.
DELETE/token/{id}Revoke a token.
POST/telegram/tautanLink for connecting Telegram (30 minutes).
DELETE/telegramDisconnect Telegram.
ServersGET/serverList servers with their latest status.
POST/serverCreate an install command. Body {"nama"}.
GET/server/{id}Details of one server.
PATCH/server/{id}Rename. Body {"nama"}.
GET/server/{id}/metrik?jam=24Metrics history from the server (max. 720 hours).
GET/server/{id}/tugasThe last 50 tasks on this server.
POST/server/{id}/perbaikiBody {"jenis": "swap" | "firewall-cek" | "firewall", "port": ["80/tcp"]}.
GET/server/{id}/lepasRelease plan: everything that will be removed.
DELETE/server/{id}Release the server. Body {"konfirmasi": "<nama server>", "simpan_cadangan": true}.
AppsGET/resepCatalog of ready-to-install apps (no sign-in needed).
POST/server/{id}/aplikasiInstall an app. Body {"resep", "nama", "variabel"}.
POST/server/{id}/aplikasi/{nama}/mulai-ulangRestart an app.
GET/server/{id}/aplikasi/{nama}/log?baris=100Recent app logs.
POST/server/{id}/aplikasi/{nama}/aksiManagement actions, for example {"aksi": "akses.daftar"}.
DELETE/server/{id}/aplikasi/{nama}Remove an app. Body {"konfirmasi": "<nama aplikasi>"}.
DatabasesGET/databaseList clusters.
POST/databaseCreate a cluster. Body {"mesin", "nama", "mode", "data": [id, id], "saksi": id}.
GET/database/{id}Details, member status, and connection string.
POST/database/{id}/sandiRead the password directly from a data server.
POST/database/{id}/proxyConnect an app server. Body {"server_id"}.
DELETE/database/{id}/proxy/{server}Remove the proxy from one app server.
DELETE/database/{id}Delete a cluster. Body {"konfirmasi": "<nama klaster>"}.
Load balancersGET/lbList load balancers.
POST/lbCreate a load balancer.
GET/lb/{id}Details and target health as seen from each balancer.
PATCH/lb/{id}Change the firewall (proxy mode). Body {"izinkan": ["IP/CIDR"], "tolak": ["IP/CIDR"]} (allow, deny). See Firewall.
DELETE/lb/{id}Delete. Body {"konfirmasi": "<nama lb>"}.
SitesGET/situsList sites (can be filtered with ?server_id=).
POST/situsCreate a site. Body {"nama", "server_id", "jenis", "domain", "php", "bahasa", "db", "klaster_id", "sumber", "cpanel"}; cpanel holds {"host", "pengguna", "sandi" or "token", "abaikan_tls"} to sign in to the old cPanel. See Sites.
GET/situs/{id}Details and status: DNS, HTTPS, backups, SFTP without password.
PATCH/situs/{id}Change the name, domains, PHP version, or PHP settings. Body {"nama", "domain", "php", "php_ini"}; php_ini: {"unggah_mb", "memori_mb", "waktu_detik", "input_vars", "zona_waktu", "tampil_galat"}.
DELETE/situs/{id}Delete. Body {"konfirmasi": "<site name>", "simpan_cadangan": true}.
POST/situs/{id}/rahasiaRead the database, SFTP, and WordPress admin passwords directly from the server.
POST/situs/{id}/cadanganBack up now.
POST/situs/{id}/pulihkanRestore. Body {"berkas": "<backup name>", "konfirmasi": "<site name>"}.
POST/situs/{id}/phpmyadminStart the site's phpMyAdmin. Returns {"url"}, a single-use sign-in link (60 seconds).
POST/situs/{id}/pindahMove to another server, copy step. Body {"server_id", "php", "perbarui_wp"}. Track pindah.keadaan in GET /situs/{id}: menyalin (copying), disalin (copied), memindah (moving), dipindah (moved).
POST/situs/{id}/pindah/sekarangMove now (the old site shows a maintenance page briefly). Body {"konfirmasi": "<site name>"}.
POST/situs/{id}/pindah/selesaiFinish: delete the site on the old server (last backup kept in /root).
POST/situs/{id}/pindah/batalCancel the move; the site stays on, or goes back to, the old server.
Feature requestsGET/fiturList feature requests with their status, balasan (reply), number of suara (supporters), and whether you already support each one.
POST/fiturRequest a feature. Body {"judul", "isi", "kategori"} (title, description, area); title 5 to 120 characters, at most 10 requests per 24 hours.
POST/fitur/{id}/suaraSupport a request, or withdraw your support if you already did.
EnterprisePOST/enterpriseAsk for migration help (no token needed). Body {"nama", "kontak", "asal", "skala", "kebutuhan"}; kontak (contact) is required, asal (moving from): aws, gcp, azure, cpanel, vps, or lain (other).
Tasks & alertsGET/tugas/{id}Task state, progress, and result.
GET/peringatan?aktif=1Alerts (active ones only when aktif=1).

Actions that delete or replace content (apps, clusters, load balancers, sites, restoring a site, moving a site now, releasing a server) reject the request with the code perlu-konfirmasi until the konfirmasi field contains the resource's name. Show the user what will be deleted first.

curl examples

List servers

curl
curl -s https://app.saka.work/api/v1/server \
  -H "Authorization: Bearer $SAKA_TOKEN"

The response contains server: each server has an id (for example srv_…), nama, ip, os, terhubung, and status (CPU, RAM, disk, apps, readiness).

Create a database cluster

curl
curl -s -X POST https://app.saka.work/api/v1/database \
  -H "Authorization: Bearer $SAKA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mesin": "postgresql",
    "nama": "toko-produksi",
    "mode": "sinkron",
    "data": ["srv_aaaaaaaaaaaaaaaa", "srv_bbbbbbbbbbbbbbbb"],
    "saksi": "srv_cccccccccccccccc"
  }'

mesin: postgresql (default) or mariadb. mode: asinkron (default) or sinkron; MariaDB is always synchronous. The 202 response contains the cluster with keadaan: "dibuat". To follow it:

curl
curl -s https://app.saka.work/api/v1/database/<id-klaster> \
  -H "Authorization: Bearer $SAKA_TOKEN"

Create a load balancer

curl
curl -s -X POST https://app.saka.work/api/v1/lb \
  -H "Authorization: Bearer $SAKA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "nama": "toko-lb",
    "mode": "proxy",
    "domain": ["toko.contoh.id"],
    "penyeimbang": ["srv_1111111111111111", "srv_2222222222222222"],
    "tujuan": ["srv_3333333333333333", "srv_4444444444444444"],
    "cadangan": [],
    "port_tujuan": 8080,
    "kebijakan": "bergiliran",
    "sticky": false,
    "path_sehat": "/"
  }'

mode: proxy (default) or dns (no domain). kebijakan: bergiliran (round robin) or koneksi_tersedikit (least connections). port_tujuan defaults to 80. penyeimbang (balancers): 1 or 2 servers. Optional in proxy mode: izinkan (allow) and tolak (deny), lists of IPs/CIDRs for the firewall.

Run a fix, then follow the task

curl
curl -s -X POST https://app.saka.work/api/v1/server/<id-server>/perbaiki \
  -H "Authorization: Bearer $SAKA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jenis": "swap"}'
curl
curl -s https://app.saka.work/api/v1/tugas/<id-tugas> \
  -H "Authorization: Bearer $SAKA_TOKEN"

The response contains tugas (id, perintah, keadaan: antre, berjalan, selesai, or gagal, lewat: panel, api, or mcp), kemajuan (the step currently running), and hasil once it is finished.