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
| Item | Value |
|---|---|
| Base URL | https://app.saka.work/api/v1 |
| Authentication | Authorization: Bearer saka_… (create one in Settings, see Create a token) |
| Format | JSON. Send Content-Type: application/json for requests that change something. |
| Request fields | Unknown fields are rejected, so typos are not silently ignored. |
| Language | Accept-Language: en or id (the default) for error, progress, and note text. See Response language. |
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:
{
"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."
}
}kode(code): fixed, for programs to match on.arti(meaning): a short explanation.langkah(next step): what you should do (may be empty).detail: the raw technical message from the server, if any.
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 -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.
| Group | Method | Path | Details |
|---|---|---|---|
| Account | GET | /akun | Account profile and Telegram status. |
GET | /token | List API tokens. | |
POST | /token | Create a token. Body {"nama"}. The secret is shown only once. | |
DELETE | /token/{id} | Revoke a token. | |
POST | /telegram/tautan | Link for connecting Telegram (30 minutes). | |
DELETE | /telegram | Disconnect Telegram. | |
| Servers | GET | /server | List servers with their latest status. |
POST | /server | Create an install command. Body {"nama"}. | |
GET | /server/{id} | Details of one server. | |
PATCH | /server/{id} | Rename. Body {"nama"}. | |
GET | /server/{id}/metrik?jam=24 | Metrics history from the server (max. 720 hours). | |
GET | /server/{id}/tugas | The last 50 tasks on this server. | |
POST | /server/{id}/perbaiki | Body {"jenis": "swap" | "firewall-cek" | "firewall", "port": ["80/tcp"]}. | |
GET | /server/{id}/lepas | Release plan: everything that will be removed. | |
DELETE | /server/{id} | Release the server. Body {"konfirmasi": "<nama server>", "simpan_cadangan": true}. | |
| Apps | GET | /resep | Catalog of ready-to-install apps (no sign-in needed). |
POST | /server/{id}/aplikasi | Install an app. Body {"resep", "nama", "variabel"}. | |
POST | /server/{id}/aplikasi/{nama}/mulai-ulang | Restart an app. | |
GET | /server/{id}/aplikasi/{nama}/log?baris=100 | Recent app logs. | |
POST | /server/{id}/aplikasi/{nama}/aksi | Management actions, for example {"aksi": "akses.daftar"}. | |
DELETE | /server/{id}/aplikasi/{nama} | Remove an app. Body {"konfirmasi": "<nama aplikasi>"}. | |
| Databases | GET | /database | List clusters. |
POST | /database | Create a cluster. Body {"mesin", "nama", "mode", "data": [id, id], "saksi": id}. | |
GET | /database/{id} | Details, member status, and connection string. | |
POST | /database/{id}/sandi | Read the password directly from a data server. | |
POST | /database/{id}/proxy | Connect 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 balancers | GET | /lb | List load balancers. |
POST | /lb | Create 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>"}. | |
| Sites | GET | /situs | List sites (can be filtered with ?server_id=). |
POST | /situs | Create 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}/rahasia | Read the database, SFTP, and WordPress admin passwords directly from the server. | |
POST | /situs/{id}/cadangan | Back up now. | |
POST | /situs/{id}/pulihkan | Restore. Body {"berkas": "<backup name>", "konfirmasi": "<site name>"}. | |
POST | /situs/{id}/phpmyadmin | Start the site's phpMyAdmin. Returns {"url"}, a single-use sign-in link (60 seconds). | |
POST | /situs/{id}/pindah | Move 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/sekarang | Move now (the old site shows a maintenance page briefly). Body {"konfirmasi": "<site name>"}. | |
POST | /situs/{id}/pindah/selesai | Finish: delete the site on the old server (last backup kept in /root). | |
POST | /situs/{id}/pindah/batal | Cancel the move; the site stays on, or goes back to, the old server. | |
| Feature requests | GET | /fitur | List feature requests with their status, balasan (reply), number of suara (supporters), and whether you already support each one. |
POST | /fitur | Request a feature. Body {"judul", "isi", "kategori"} (title, description, area); title 5 to 120 characters, at most 10 requests per 24 hours. | |
POST | /fitur/{id}/suara | Support a request, or withdraw your support if you already did. | |
| Enterprise | POST | /enterprise | Ask 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 & alerts | GET | /tugas/{id} | Task state, progress, and result. |
GET | /peringatan?aktif=1 | Alerts (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 -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 -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 -s https://app.saka.work/api/v1/database/<id-klaster> \
-H "Authorization: Bearer $SAKA_TOKEN"Create a load balancer
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 -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 -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.