Schema Builder API
API dan menu Database Tables di Studio untuk membuat dan mengelola tabel transaksi milik tenant sendiri (DDL/DML) tanpa menulis SQL mentah.
Gambaran Umum
Schema Builder memungkinkan tenant membuat dan mengelola tabel data miliknya sendiri di database AlurKerja — tanpa menulis SQL. Cocok dipakai SOP/integrasi eksternal yang butuh tabel transaksi kustom (mis. tabel yang dipakai bersama beberapa proses BPMN, atau data yang perlu dikelola lewat API alih-alih lewat form). Tabel bisa dikelola lewat REST API (halaman ini) atau lewat menu Database Tables di Studio (lihat Mengelola dari Studio).
Setiap tenant punya schema database sendiri. API ini hanya mengizinkan operasi pada tabel yang dibuat lewat API ini sendiri — tabel bawaan sistem (mis. tabel transaksi otomatis per proses BPMN, lihat Tabel Transaksi Manual) tidak pernah bisa disentuh atau tertimpa oleh API ini.
Endpoint ini hanya bisa diakses oleh Admin SOP tenant (bukan Member biasa), karena bisa membuat/mengubah/menghapus tabel dan datanya.
Autentikasi
Semua endpoint butuh header Authorization: Bearer <token> (JWT AlurKerja, sama seperti endpoint API lain) dan :tenantId di path adalah ID tenant tujuan.
Semua respons memakai amplop yang sama: success, message (kalau berhasil), data, dan error (kalau gagal). Pada contoh di bawah, {tenantId} dan {table} diganti dengan ID tenant dan nama tabel Anda.
Selalu aktif
Seluruh fitur di halaman ini — tipe varchar(N), endpoint /check, endpoint /references, dan menu Database Tables di Studio — selalu aktif di report-service dan Studio. Tidak ada lagi feature flag yang perlu dinyalakan, dan instalasi on-premise baru langsung mendapatkan fiturnya tanpa konfigurasi tambahan.
Untuk admin instalasi
Variabel FEATURE_FLAG_DATABASE_TABLES (report-service) dan VITE_FEATURE_FLAG_DATABASE_TABLES (Studio) sudah tidak dibaca lagi. Instalasi yang pernah menyetelnya, baik true maupun false, tidak perlu mengubah apa pun: nilai lama diabaikan dan boleh dihapus dari .env, values Helm, atau environment container. Perilaku untuk instalasi yang sebelumnya menyalakan flag ini tidak berubah.
Tipe kolom yang didukung
Saat membuat tabel atau menambah kolom, tiap kolom harus salah satu dari tipe berikut:
| Tipe (dikirim di request) | Disimpan sebagai |
|---|---|
text | teks bebas panjang |
varchar | teks dengan batas panjang N karakter. Wajib disertai length 1–10.000 |
integer | bilangan bulat |
bigint | bilangan bulat besar |
numeric | angka desimal presisi |
boolean | benar/salah |
date | tanggal |
timestamp | tanggal + jam (dengan timezone) |
jsonb | data JSON bebas |
Tiap tabel otomatis punya 3 kolom bawaan yang tidak perlu (dan tidak boleh) didefinisikan sendiri: id (primary key), created_at, updated_at.
Nama tabel dan nama kolom harus huruf kecil, diawali huruf, hanya huruf/angka/underscore (^[a-z][a-z0-9_]*$), maksimal 63 karakter. Nama tabel tidak boleh diawali catalog_, pg_, atau report_. Jumlah kolom per tabel dibatasi pengaturan instalasi SCHEMA_BUILDER_MAX_COLUMNS (bawaan 100); nilai yang berlaku dikembalikan di field max_columns pada setiap respons tabel.
varchar(N)
varchar dikirim sebagai dua field terpisah: type berisi "varchar" dan length berisi bilangan bulat 1 sampai 10.000. Panjang lebih dari itu pakai text.
{ "name": "kode_cabang", "type": "varchar", "length": 20, "nullable": false }lengthwajib untukvarchardan ditolak untuk tipe lain (400).lengthharus bilangan bulat JSON."20"atau20.5dijawab 400length must be an integer.- Menulis nilai yang lebih panjang dari
lengthlewat Tambah data atau Ubah data dijawab 400value for column "kode_cabang" is longer than 20 characters. Data tidak pernah dipotong. - Di respons, kolom
varcharmembawa fieldlength; field itu tidak muncul untuk tipe lain.
Tabel
Buat tabel
POST /api/v1/report/{tenantId}/schema/tables{
"name": "penjualan_harian",
"description": "Rekap penjualan harian per cabang",
"columns": [
{ "name": "cabang", "type": "text", "nullable": false },
{ "name": "total", "type": "numeric", "nullable": false },
{ "name": "catatan", "type": "text", "nullable": true }
]
}{
"success": true,
"message": "Table created successfully",
"data": {
"id": 1,
"table_name": "penjualan_harian",
"columns": [
{ "name": "cabang", "type": "text", "nullable": false },
{ "name": "total", "type": "numeric", "nullable": false },
{ "name": "catatan", "type": "text", "nullable": true }
],
"description": "Rekap penjualan harian per cabang",
"created_at": "2026-09-23T10:00:00Z",
"updated_at": "2026-09-23T10:00:00Z",
"max_columns": 100
}
}Request ditolak (400) kalau nama tabel sudah dipakai tabel aktif lain di schema tenant ini — baik tabel yang dibuat lewat API ini sebelumnya, maupun tabel bawaan sistem (mis. tabel transaksi proses BPMN). Ini mencegah tabel baru menimpa data yang sudah ada. Nama tabel yang sudah dihapus boleh dipakai lagi untuk tabel baru.
Daftar tabel
GET /api/v1/report/{tenantId}/schema/tablesMengembalikan semua tabel yang dibuat tenant ini lewat API ini (tabel bawaan sistem tidak ikut muncul).
Detail tabel
GET /api/v1/report/{tenantId}/schema/tables/{table}Mengembalikan definisi kolom tabel. 404 kalau tabel tidak pernah dibuat lewat API ini (termasuk kalau nama itu adalah tabel bawaan sistem).
Hapus tabel
DELETE /api/v1/report/{tenantId}/schema/tables/{table}?confirm=trueParameter confirm=true wajib — tanpa itu, request ditolak (400) dan tabel tidak disentuh sama sekali. Ini mencegah tabel terhapus tidak sengaja.
Ini adalah DROP TABLE, bukan rename atau backup — seluruh baris di tabel itu ikut hilang. Kalau datanya masih dipakai, ekspor dulu lewat Lihat data. Setelah dihapus, nama tabelnya bebas dan boleh dipakai lagi.
Menghapus tabel tidak diblokir oleh Question yang masih memakainya. Question akan gagal dijalankan setelah tabelnya hilang, jadi periksa dulu lewat Question yang memakai tabel.
Kolom
Struktur tabel yang sudah dibuat bisa diubah lewat endpoint kolom berikut. Semua perubahan DDL dan pembaruan registry berjalan dalam satu transaksi: kalau gagal, tidak ada yang berubah.
| Endpoint | Fungsi |
|---|---|
POST …/schema/tables/{table}/columns | Tambah kolom |
DELETE …/schema/tables/{table}/columns/{column}?confirm=true | Hapus kolom |
POST …/schema/tables/{table}/columns/{column} | Ubah nama, nullable, dan/atau tipe kolom |
POST …/schema/tables/{table}/columns/{column}/check | Periksa dulu perubahan tipe/nullable tanpa menerapkannya |
Kolom bawaan id, created_at, dan updated_at tidak bisa diubah atau dihapus.
Tambah kolom
POST /api/v1/report/{tenantId}/schema/tables/{table}/columns{ "name": "kode_cabang", "type": "varchar", "length": 50 }| Field | Wajib | Keterangan |
|---|---|---|
name | ya | Nama kolom baru; aturan penamaan sama seperti di atas, tidak boleh sama dengan kolom lain. |
type | ya | Salah satu tipe kolom. |
length | untuk varchar | 1–10.000. Ditolak untuk tipe lain. |
copy_from | tidak | Nama kolom lain di tabel yang sama. Nilainya disalin (di-cast ke tipe baru) ke kolom baru, dalam transaksi yang sama. |
Kolom yang ditambah selalu nullable — tidak ada opsi nullable, karena menambah kolom NOT NULL ke tabel yang sudah berisi baris akan gagal di Postgres. Pakai ubah kolom dengan "nullable": false setelah semua baris terisi.
{
"success": true,
"message": "Column added successfully",
"data": {
"id": 3,
"table_name": "cabang",
"columns": [
{ "name": "nama", "type": "text", "nullable": false },
{ "name": "kode_cabang", "type": "varchar", "nullable": true, "length": 50 }
],
"created_at": "2026-10-06T09:00:00Z",
"updated_at": "2026-10-08T03:15:00Z",
"max_columns": 100
}
}Kalau copy_from dipakai, respons ikut membawa "affected_rows" (jumlah baris yang disalin). Kalau nilai sumber tidak muat di kolom varchar(N) yang baru, request dijawab 409 (lihat konflik data) dan kolom baru tidak dibuat.
Error yang mungkin: 400 kalau nama/tipe tidak valid, nama sudah dipakai, atau jumlah kolom melewati max_columns; 404 kalau tabel tidak ada (kolom copy_from yang tidak ada dijawab 400); 409 kalau tabel sudah memakai habis 1.600 slot kolom milik Postgres (termasuk kolom yang sudah dihapus) — buat tabel baru dan pindahkan datanya.
Hapus kolom
DELETE /api/v1/report/{tenantId}/schema/tables/{table}/columns/{column}?confirm=trueconfirm=true wajib. Tanpa itu, tidak ada yang dihapus: kalau ada Question yang menyebut kolom itu, jawabannya 409 berisi daftar Question; kalau tidak ada, 400 minta confirm=true.
{
"success": false,
"error": "2 report question(s) reference this column; check them, then pass confirm=true to proceed anyway",
"data": {
"questions": [
{ "id": 12, "name": "Penjualan per cabang" },
{ "id": 31, "name": "Top 10 cabang" }
]
}
}{
"success": true,
"message": "Column dropped successfully (863 row(s) affected)",
"data": {
"table_name": "cabang",
"column_name": "kode_cabang",
"affected_rows": 863,
"referencing_questions": [
{ "id": 12, "name": "Penjualan per cabang" }
]
}
}affected_rows adalah jumlah baris yang kehilangan data kolom itu. Penghapusan kolom tidak bisa dibatalkan. referencing_questions hanya muncul kalau ada Question yang menyebut kolom itu walau confirm=true sudah meloloskan penghapusan. Kolom terakhir di tabel tidak bisa dihapus (400).
Ubah kolom
POST /api/v1/report/{tenantId}/schema/tables/{table}/columns/{column}Satu request boleh berisi kombinasi new_name, nullable, dan type (+ length); minimal satu wajib. Urutan penerapannya tetap: tipe → nullable → nama, semuanya dalam satu transaksi.
| Field | Keterangan |
|---|---|
new_name | Nama baru kolom. Wajib disertai ?confirm=true (lihat di bawah). |
nullable | true untuk mengizinkan kosong, false untuk mewajibkan nilai. |
type | Tipe baru, sesuai aturan ubah tipe. |
length | Panjang untuk type: "varchar". Hanya boleh bersama type. |
{ "type": "varchar", "length": 20 }Contoh ini dijalankan pada kolom kode_cabang bertipe varchar(50) di tabel cabang. Mengecilkan varchar adalah perubahan yang diperiksa dulu: report-service mengunci tabel, lalu menghitung baris yang nilainya lebih panjang dari 20 karakter.
Kalau semua nilai muat, perubahan diterapkan:
{
"success": true,
"message": "Column updated successfully",
"data": {
"id": 3,
"table_name": "cabang",
"columns": [
{ "name": "nama", "type": "text", "nullable": false },
{ "name": "kode_cabang", "type": "varchar", "nullable": true, "length": 20 }
],
"created_at": "2026-10-06T09:00:00Z",
"updated_at": "2026-10-08T03:20:00Z",
"max_columns": 100
}
}Kalau masih ada nilai yang lebih panjang, perubahan ditolak dan tidak ada data yang dipotong atau diubah:
{
"success": false,
"error": "12 rows are longer than 20 characters",
"data": {
"reason": "too_long",
"violating_rows": 12,
"sample_ids": [4, 17, 23],
"max_length": 30
}
}violating_rows adalah jumlah baris yang tidak muat, sample_ids berisi paling banyak 3 id contoh, dan max_length adalah nilai terpanjang yang ada di kolom (di sini 30 karakter, jadi varchar(30) atau lebih besar masih lolos). Perbaiki atau pendekkan baris itu dulu, atau pilih panjang yang lebih besar, lalu ulangi.
Angka pada contoh 409 (12 baris, id 4/17/23, panjang maksimum 30) hanya ilustrasi; angka sebenarnya bergantung isi tabel Anda. Bentuk respons mengikuti kode report-service. Gunakan /check untuk melihat hasil yang sama tanpa menerapkan perubahan.
Ganti nama kolom
Mengganti nama kolom bisa merusak Question yang menulis nama lama di SQL-nya, jadi new_name selalu butuh ?confirm=true. Tanpa confirm=true, tidak ada yang berubah: kalau ada Question yang menyebut kolom itu, jawabannya 409 berisi daftarnya (bentuk sama seperti Hapus kolom); kalau tidak ada, 400 renaming a column requires confirm=true.
POST /api/v1/report/{tenantId}/schema/tables/{table}/columns/{column}?confirm=true{ "new_name": "kode" }Respons 200 sama seperti di atas, ditambah referencing_questions kalau ada Question yang menyebut kolom itu. Question tidak ikut diperbarui otomatis; perbarui SQL-nya sendiri.
Jadikan kolom wajib diisi
{ "nullable": false }Kalau masih ada baris yang kosong di kolom itu, perubahan ditolak dengan jawaban 409 yang memuat jumlah baris dan id contoh:
{
"success": false,
"error": "863 rows have no value in catatan",
"data": { "reason": "has_nulls", "violating_rows": 863, "sample_ids": [1, 2, 3] }
}Konflik data
Respons 409 dengan data.reason berarti ada baris yang menghalangi perubahan:
reason | Arti | Field lain |
|---|---|---|
too_long | Ada nilai yang lebih panjang dari varchar tujuan | violating_rows, sample_ids, max_length |
has_nulls | Ada baris kosong, sehingga kolom tidak bisa dibuat wajib | violating_rows, sample_ids |
409 juga dipakai untuk the table is busy with another operation, try again. Ubah tipe menulis ulang tabel dengan lock eksklusif; kalau tabel sedang dipakai transaksi lain lebih dari 5 detik, atau perubahan berjalan lebih dari 60 detik, perubahan dibatalkan. Ulangi beberapa saat kemudian.
Periksa perubahan kolom
POST /api/v1/report/{tenantId}/schema/tables/{table}/columns/{column}/checkMenjawab "apakah perubahan ini akan diterima?" memakai hitungan baris yang sama dengan perubahan sungguhan, tetapi tidak mengubah apa pun. Body-nya type (+ length) dan/atau nullable; minimal salah satu wajib (new_name tidak diperiksa di sini).
{ "type": "varchar", "length": 20 }Perubahan yang akan diterima:
{
"success": true,
"message": "Column change checked",
"data": { "allowed": true }
}Perubahan yang akan ditolak karena data:
{
"success": true,
"message": "Column change checked",
"data": {
"allowed": false,
"reason": "too_long",
"violating_rows": 12,
"sample_ids": [4, 17, 23],
"max_length": 30
}
}Perhatikan bahwa /check menjawab 200 baik allowed bernilai true maupun false; pelanggaran data tidak dijawab 409 di sini. Kalau tipe tujuan memang tidak diizinkan oleh aturan ubah tipe, jawabannya 400 dengan pesan penjelas. Bila type dan nullable dikirim bersamaan, yang dilaporkan adalah masalah pertama yang ditemukan, tipe lebih dulu baru nullable. Studio memanggil endpoint ini sebelum menyimpan perubahan tipe atau Make required.
Aturan ubah tipe
Tidak semua perubahan tipe boleh langsung dijalankan. Aturan dibagi tiga: langsung (tidak mungkin gagal karena data), diperiksa dulu (bisa gagal karena data, jadi dihitung dulu), dan ditolak.
| Dari → ke | Aturan |
|---|---|
varchar(N) → varchar(M), M > N | Langsung diizinkan |
varchar(N) → text | Langsung diizinkan |
varchar(N) → varchar(M), M < N | Diperiksa dulu |
text → varchar(M) | Diperiksa dulu |
tipe lain yang dikenal → varchar(M) | Diperiksa dulu |
varchar(N) → varchar(N) (sama) | Ditolak: column is already type "varchar(N)" |
varchar → integer, date, dan tipe lain selain text | Ditolak |
Diperiksa dulu artinya: di dalam transaksi dan lock yang sama dengan ALTER, report-service menghitung baris yang nilainya lebih panjang dari M. Kalau ada satu saja, perubahan ditolak 409 dengan jumlah baris dan beberapa contoh id. Data tidak pernah dipotong.
Untuk tipe selain varchar, daftar perubahan yang langsung diizinkan adalah:
| Dari | Boleh ke |
|---|---|
integer | bigint, numeric, text |
bigint | numeric, text |
numeric | text |
boolean | text |
date | text, timestamp |
timestamp | text |
jsonb | text |
Perubahan date → timestamp memakai tengah malam zona waktu Asia/Jakarta, tidak bergantung pada pengaturan zona waktu sesi database.
Kombinasi lain — misalnya text → integer, numeric → integer, timestamp → date, atau text → jsonb — ditolak (400) karena bisa gagal di tengah data atau mengubah arti data. Jalurnya: tambah kolom baru dengan tipe tujuan dan copy_from kolom lama, periksa hasilnya, lalu hapus kolom lama. Kalau penyalinan gagal, transaksi dibatalkan dan kolom baru tidak pernah ada.
Jangan menambah atau mengubah kolom secara manual lewat psql langsung ke schema tenant. Endpoint Data (rows) memvalidasi nama kolom terhadap definisi yang tersimpan di registry internal, bukan terhadap struktur tabel yang sebenarnya di database — jadi kolom hasil ALTER manual tetap ditolak (400, unknown column "...") walaupun kolomnya benar-benar ada di database. Sebaliknya, kolom yang dihapus manual lewat psql membuat request tambah/ubah data gagal di level database. Registry dan tabel fisik harus tetap sejalan; gunakan endpoint kolom di atas.
Question yang memakai tabel
GET /api/v1/report/{tenantId}/schema/tables/{table}/referencesMengembalikan Question yang SQL-nya menyebut nama tabel ini (sebagai kata utuh, tidak membedakan huruf besar/kecil), beserta satu baris cuplikan SQL, dan untuk tiap kolom tabel berapa Question di antaranya yang menyebut kolom itu (0 tetap ditampilkan). Studio memakainya untuk kolom Used in dan untuk daftar di dialog hapus tabel.
{
"success": true,
"message": "Table references retrieved successfully",
"data": {
"questions": [
{
"id": 12,
"name": "Penjualan per cabang",
"snippet": "SELECT kode_cabang, COUNT(*) FROM <<cabang>> GROUP BY kode_cabang",
"updated_at": "2026-10-02T08:30:00Z"
}
],
"columns": {
"nama": 0,
"kode_cabang": 1
}
}
}snippet adalah baris pertama SQL yang menyebut tabel (maksimal 200 karakter). updated_at bernilai null untuk Question lama yang tidak punya cap waktu. 404 kalau tabel tidak ada.
Pemeriksaan ini berbasis teks: report-service mencari nama tabel dan kolom di teks SQL Question, bukan menganalisis SQL-nya, dan tidak ada relasi tersimpan antara Question dan tabel. Hasilnya adalah peringatan, bukan jaminan — Question yang menyusun nama tabel secara dinamis tidak terdeteksi, dan nama yang kebetulan sama dengan kata lain di SQL bisa ikut terhitung. Endpoint ini hanya informasi dan tidak pernah memblokir hapus tabel.
Pemeriksaan serupa sudah ada di Hapus kolom dan Ganti nama kolom, yang menjawab 409 berisi daftar Question sampai confirm=true dikirim.
Mengelola dari Studio
Selain lewat API, tabel bisa dikelola dari menu Database Tables di Studio, di grup DESIGN tepat di bawah Master Data. Menu ini selalu tampil, dan hanya bisa dipakai Admin SOP — Member biasa tidak punya akses ke Studio. Semua aksi di bawah memanggil endpoint yang dijelaskan di halaman ini.
Daftar tabel dan buat tabel
Saat pertama kali dibuka dan belum ada tabel, halaman Database Tables menampilkan keadaan kosong. Di bagian atas ada penjelasan singkat Master Data or Database Tables? beserta tautan Go to Master Data, lalu pesan No database tables yet yang menjelaskan bahwa tabel dibuat untuk data yang ditulis oleh integrasi atau proses, dan dibaca Question sebagai <<table_name>>. Dari sini Anda bisa langsung membuat tabel pertama.
Klik New table.
Isi Name tabel. Nama divalidasi di form dengan aturan yang sama seperti API (huruf kecil, diawali huruf, hanya huruf/angka/underscore). Di bawahnya tertulis bentuk yang dipakai Question: <<nama_tabel>>. Isi Description bila perlu.
Di bagian Columns, tambahkan baris kolom: Name, Type, Length (aktif hanya untuk varchar, isi 1–10.000), dan Nullable. Tipe yang bisa dipilih adalah text, varchar, integer, bigint, numeric, boolean, date, dan timestamp. Kolom id, created_at, dan updated_at ditambahkan otomatis.
Simpan. Tabel baru muncul di daftar dan detailnya terbuka.
Setelah ada tabel, halaman menampilkan semuanya dengan kolom Name, Description, Columns (jumlah kolom), dan Created. Kotak Search tables... menyaring daftar, dan penjelasan singkat beserta tautan ke Master Data tetap tampil di atas daftar.
Kelola kolom di detail tabel
Klik sebuah tabel untuk membuka detailnya. Di bagian atas ada nama tabel, tombol <<nama_tabel>> untuk menyalin nama yang dipakai di Question, dan menu Table actions. Tab Columns menampilkan kolom dengan Name, Type, Nullable, dan Used in (jumlah Question yang menyebut kolom itu, dari endpoint /references). Kolom bawaan tampil di bawah label Added automatically dan tidak bisa diubah.
Tambah kolom. Klik tambah kolom, isi Name, Type, dan Length untuk varchar. Kolom baru selalu nullable, sama seperti di API.
Aksi per kolom. Menu di ujung baris kolom berisi Rename, Change type, Make required (atau Make optional untuk kolom yang sudah wajib), dan Delete column.
- Rename dan Delete column menampilkan Question yang menyebut kolom itu sebelum Anda mengonfirmasi.
- Make required memeriksa dulu lewat
/check. Kalau masih ada baris kosong, dialog menolak dan menampilkan jumlah baris beserta contohid, sehingga Anda bisa mengisinya lebih dulu. - Change type hanya menawarkan tipe tujuan yang sah dari tipe asal sesuai aturan ubah tipe. Sebelum Save, Studio memanggil
/check; kalau ada nilai yang terlalu panjang, dialog menampilkan jumlah baris, contohid, dan panjang maksimum yang ada, mis. "12 baris lebih panjang dari 20 karakter". Untuk perubahan lain, dialog mengarahkan ke jalur tambah kolom baru → salin data → hapus kolom lama.
Kalau tabel sedang sibuk, Studio menampilkan pesan bahwa tabel sedang dipakai operasi lain; ulangi beberapa saat kemudian.
Hapus tabel
Buka Table actions → Delete table. Dialog menampilkan jumlah baris yang akan hilang, mengingatkan bahwa integrasi dan proses yang menulis ke tabel itu akan gagal, dan memeriksa Question yang menyebut tabel itu (Checking questions…) lalu menampilkannya. Tombol Delete table baru aktif setelah Anda mengetik nama tabel persis di kotak konfirmasi. Penghapusan tidak bisa dibatalkan; nama tabel yang sudah dihapus boleh dipakai lagi.
Tab Data
Tab Data menampilkan isi tabel dalam mode hanya baca: ada paginasi dan kotak filter = value di tiap kolom, tetapi tidak ada tombol tambah, ubah, atau hapus baris. Nilai kosong ditampilkan sebagai NULL. Isi tabel ditulis oleh integrasi dan proses lewat API data. Bila tabel masih kosong, tertulis No rows yet.
Pakai tabel di Question
Di editor Question, tabel Schema Builder muncul di grup Database Tables pada pemilih tabel, terpisah dari tabel proses dan Master Data. Question menyebut tabel dengan namanya, mis. <<penjualan_harian>>.
Database Tables dan Master Data
| Master Data | Database Tables (Schema Builder) | |
|---|---|---|
| Untuk apa | Data referensi form: pilihan dropdown, relasi | Tabel SQL untuk integrasi dan laporan |
| Penyimpanan | Record JSON di bpm-service | Tabel Postgres sungguhan di schema tenant |
| Tipe kolom | 14 tipe tampilan (select, file, email, rich text…) | 8 tipe SQL ditambah varchar(N) |
| Isi data | Lewat Studio, termasuk impor XLSX | Lewat API dan proses; Studio hanya menampilkan |
| Dibaca Question | Ya, sebagai master_<slug> | Ya, dengan nama tabelnya |
Aturan praktisnya: kalau datanya dipilih di form, pakai Master Data. Kalau datanya ditulis sistem lain atau proses dan dibaca laporan, pakai Database Tables.
Data (rows)
Tambah data
POST /api/v1/report/{tenantId}/schema/tables/{table}/rows{
"rows": [
{ "cabang": "Surabaya", "total": 1250000, "catatan": null },
{ "cabang": "Malang", "total": 870500, "catatan": "termasuk diskon" }
]
}Setiap baris dalam satu request harus punya kolom yang persis sama (tidak boleh baris 1 punya kolom catatan sementara baris 2 tidak). Satu request menampung 1–500 baris.
{ "success": true, "message": "Rows inserted successfully", "data": { "affected_rows": 2 } }Lihat data
GET /api/v1/report/{tenantId}/schema/tables/{table}/rows?page=0&limit=50&cabang=Surabayapage(default0) danlimit(default50, maksimal500) untuk paginasi.- Parameter query lain diperlakukan sebagai filter kesamaan (
kolom=nilai), boleh lebih dari satu (digabung dengan AND). Filter pada kolom yang tidak ada di tabel akan ditolak (400).
{
"success": true,
"message": "Rows retrieved successfully",
"data": {
"rows": [
{ "id": 1, "cabang": "Surabaya", "total": 1250000, "catatan": null, "created_at": "...", "updated_at": "..." }
],
"page": 0,
"limit": 50,
"total": 1
}
}Ubah data
PATCH /api/v1/report/{tenantId}/schema/tables/{table}/rows{
"where": { "cabang": "Surabaya" },
"set": { "total": 1480000 }
}where dan set wajib diisi (tidak boleh kosong) — endpoint ini sengaja tidak mendukung update ke seluruh baris sekaligus tanpa kondisi, supaya update yang salah tidak menimpa semua data.
Hapus data
DELETE /api/v1/report/{tenantId}/schema/tables/{table}/rows{ "where": { "cabang": "Malang" } }where wajib diisi untuk alasan yang sama dengan update — hapus seluruh baris di satu tabel tidak didukung endpoint ini (hapus tabelnya lewat endpoint Hapus Tabel kalau memang itu maksudnya).
Error yang umum terjadi
| Status | Penyebab |
|---|---|
| 400 | Body request tidak valid, tipe kolom tidak didukung, nama tabel/kolom melanggar aturan penamaan, jumlah kolom melewati max_columns, atau kolom yang direferensikan (where/set/filter) tidak dikenal. |
| 400 | varchar tanpa length, length di luar 1–10.000 atau bukan bilangan bulat, length pada tipe selain varchar, atau nilai baris lebih panjang dari kolom varchar. |
| 400 | Hapus tabel atau kolom tanpa ?confirm=true, ganti nama kolom tanpa ?confirm=true, atau perubahan tipe yang tidak diizinkan aturan ubah tipe. |
| 401 | Token tidak ada/tidak valid, atau bukan Admin SOP. |
| 400 | Kolom yang diubah, dihapus, atau dijadikan sumber copy_from tidak ada di tabel (column "..." not found on this table). |
| 404 | Tabel tidak ditemukan — tidak pernah dibuat lewat API ini untuk tenant tersebut. |
| 409 | Perubahan kolom ditahan oleh data (too_long, has_nulls), kolom dipakai Question dan belum ada confirm=true, tabel sedang sibuk, atau slot kolom Postgres habis. |
Jejak audit
Setiap panggilan create/drop table, tambah/hapus/ubah kolom, insert, update, dan delete tercatat (siapa, kapan, statement, berhasil/gagal) untuk keperluan audit internal. Kontak tim AlurKerja kalau butuh melihat riwayat perubahan sebuah tabel.
