Dokumentasi lengkap REST API. Semua endpoint menggunakan JSON. Autentikasi menggunakan JWT Bearer Token kecuali endpoint Widget yang bersifat public.
Base URL
https://apresiasi.test/api
Cara memanggil endpoint
Path ditulis relatif (misal /auth/login). Ada dua format URL:
A. Pretty URL — Apache + mod_rewrite (htaccess aktif):
POST https://apresiasi.test/api/auth/login
B. Query route (fallback) — Nginx / Laravel Valet tanpa rewrite khusus:
POST https://apresiasi.test/api/index.php?route=auth/login GET https://apresiasi.test/api/index.php?route=transactions&page=1 GET https://apresiasi.test/api/index.php?route=widget/check-status/INV-123
Content-Type: application/json wajib disertakan.
🔒 Autentikasi JWT
Sertakan header Authorization: Bearer {token} pada setiap request yang membutuhkan autentikasi. Token didapat dari POST /auth/login.
🌐 CORS Whitelist
Hanya origin yang terdaftar di CORS_ALLOWED_ORIGINS pada .env yang diizinkan. Browser akan memblok response dari origin lain secara otomatis.
⚡ Rate Limiting Login
Maksimal 5 percobaan login gagal per IP dalam 15 menit. Jika melebihi, endpoint mengembalikan HTTP 429 Too Many Requests. Konfigurasi di .env via RATE_LIMIT_MAX_ATTEMPTS dan RATE_LIMIT_WINDOW_MINUTES.
📄 Export Token
Download CSV tidak menggunakan JWT di URL. Gunakan POST /export-tokens untuk mendapatkan one-time token (berlaku 60 detik, sekali pakai), lalu sertakan sebagai ?export_token=.
Sukses
{
"status": true,
"code": 200,
"message": "Success",
"data": { ... }
}
Error
{
"status": false,
"code": 422,
"message": "Validasi gagal",
"errors": {
"email": "Email wajib diisi"
}
}
Paginated
{
"status": true,
"code": 200,
"message": "Success",
"data": {
"items": [ ... ],
"total": 100,
"page": 1,
"per_page": 15,
"last_page": 7
}
}
/auth/login
Login dan dapatkan access token + refresh token.
URL lengkap (contoh)
POST https://apresiasi.test/api/index.php?route=auth/login
Request Body
{
"email": "admin@apresiasi.test",
"password": "admin123"
}
Response 200
{
"status": true,
"code": 200,
"message": "Login berhasil",
"data": {
"token": "eyJhbGci...",
"refresh_token": "eyJhbGci...",
"user": {
"id": 1,
"name": "Administrator",
"email": "admin@apresiasi.test",
"role": "admin",
"website_id": null
}
}
}
Response 429 (rate limit)
{
"status": false,
"code": 429,
"message": "Terlalu banyak percobaan login. Coba lagi dalam 15 menit."
}
/auth/me
🔒 Auth
— Profil user yang sedang login
/auth/refresh
Perbarui access token menggunakan refresh token.
{ "refresh_token": "eyJhbGci..." }
/auth/forgot-password
{ "email": "user@domain.com" }
/auth/reset-password
{ "token": "abc123...", "password": "newpassword" }
One-time token untuk download file CSV. Digunakan agar JWT tidak perlu diekspos di URL browser
(browser history, server log, Referer header). Token berlaku 60 detik dan hanya bisa dipakai sekali.
Konfigurasi TTL via EXPORT_TOKEN_TTL_SECONDS di .env.
/export-tokens
🔒 Auth
Buat export token baru. Langkah ini dilakukan via AJAX (tetap pakai Authorization header), hasilnya digunakan untuk redirect download.
Request Body
{
"endpoint": "transactions/export/csv",
"params": {
"status": "paid",
"date_from": "2026-01-01",
"date_to": "2026-12-31"
}
}
Response 201
{
"status": true,
"code": 201,
"message": "Export token berhasil dibuat",
"data": {
"token": "a1b2c3d4e5f6..."
}
}
Contoh alur download CSV di frontend
// 1. Ambil export token via AJAX
apiPost('export-tokens', {
endpoint: 'transactions/export/csv',
params: { status: 'paid', date_from: '2026-01-01' }
}).done(function(res) {
// 2. Redirect ke URL download dengan export_token
window.location.href = apiUrl('transactions/export/csv')
+ '&status=paid&date_from=2026-01-01'
+ '&export_token=' + res.data.token;
});
🔒 Semua endpoint memerlukan autentikasi. User website_owner hanya melihat data websitenya sendiri.
Query params opsional: date_from, date_to, period (day / week / month / year).
/dashboard/summary
Ringkasan: total pendapatan, jumlah transaksi, status breakdown, penghasilan bulan ini.
/dashboard/chart
Data chart pendapatan per periode. Label format: day → YYYY-MM-DD, week → YYYY-Www, month → YYYY-MM, year → YYYY.
/dashboard/top-websites
Top 10 website dengan penghasilan terbanyak.
/dashboard/top-articles
Top 10 artikel/konten dengan penghasilan terbanyak. Digroup by article_url.
/transactions
🔒 Auth
List transaksi dengan filter & pagination. website_owner hanya melihat transaksi websitenya.
| Param | Tipe | Keterangan |
|---|---|---|
page | int | Halaman (default: 1) |
per_page | int | Per halaman (default: 15, maks: 100) |
search | string | Cari nama, email, invoice, judul artikel |
status | string | paid / pending / expired / failed |
website_id | int | Filter per website (admin only) |
date_from | date | Dari tanggal (YYYY-MM-DD) |
date_to | date | Sampai tanggal (YYYY-MM-DD) |
/transactions/{id}
— Detail satu transaksi
/transactions/export/csv?export_token={token}
Download CSV. Gunakan Export Token (bukan JWT di URL). Mendukung filter params yang sama dengan list. Lihat seksi Export Tokens.
🔒 Semua endpoint memerlukan role admin.
/websites— List websites (search, pagination)/websites/{id}— Detail website/websites/export?export_token={token}
Download CSV. Gunakan Export Token, bukan JWT di URL.
/websites
{
"name": "Portal Berita XYZ",
"domain": "https://beritaxyz.com",
"api_endpoint": "https://beritaxyz.com/api/article",
"share_website": 50,
"share_platform": 50,
"is_active": 1
}
/websites/{id}— Update (body sama dengan POST)/websites/{id}— Soft delete (nonaktifkan)🔒 Semua endpoint memerlukan role admin.
/users— List users (search, pagination)/users/{id}— Detail user/users
{
"name": "Pemilik Web XYZ",
"email": "owner@beritaxyz.com",
"password": "password123",
"role_id": 2,
"website_id": 1,
"is_active": 1
}
/users/{id}— Update (field password opsional)/users/{id}— Nonaktifkan user🔒 Admin only.
/roles— List semua role/roles/{id}— Detail role beserta permissions/roles/permissions— List semua permission tersedia di sistem/roles
{
"name": "editor",
"description": "Editor konten",
"permissions": ["dashboard.view", "transactions.view"]
}
/roles/{id}— Update role + permissions/roles/{id}— Hapus role (gagal jika masih ada user)🔒 Admin only. Konfigurasi bersifat key-value dengan pengelompokan (config_group).
/config— List semua config (grouped)/config/{key}— Satu config by key/config— Tambah config baru/config/{key}— Update satu config/config— Bulk update banyak config sekaligusBulk Update Body
{
"configs": {
"tax_percentage": "12",
"app_name": "Nama Aplikasi Baru"
}
}
🔒 Admin only. Riwayat seluruh aktivitas pengguna di sistem (login, create, update, delete, export, dll). Setiap entry mencatat user, modul, aksi, IP address, dan user agent.
/audit-logs
🔒 Admin
List audit log dengan filter & pagination (25 per halaman).
| Param | Tipe | Keterangan |
|---|---|---|
page | int | Halaman (default: 1) |
per_page | int | Per halaman (maks: 100) |
search | string | Cari nama user, detail, atau IP address |
user_id | int | Filter per user |
action | string | login / create / update / delete / export / dll |
module | string | auth / transactions / websites / users / roles / config / dll |
date_from | date | Dari tanggal (YYYY-MM-DD) |
date_to | date | Sampai tanggal (YYYY-MM-DD) |
Contoh Response
{
"status": true,
"data": {
"items": [
{
"id": 42,
"action": "login",
"module": "auth",
"reference_id": null,
"detail": null,
"ip_address": "127.0.0.1",
"user_agent": "Mozilla/5.0 ...",
"created_at": "2026-04-21 09:15:30",
"user_name": "Administrator",
"user_email": "admin@apresiasi.test"
}
],
"total": 200,
"page": 1,
"per_page": 25
}
}
/audit-logs/export?export_token={token}
Download CSV. Gunakan Export Token (seksi Export Tokens). Maks 10.000 baris.
/audit-logs/actions
— Daftar nilai action unik untuk dropdown filter
/audit-logs/modules
— Daftar nilai module unik untuk dropdown filter
Public
Endpoint yang dipanggil JavaScript widget di website mitra. Tidak perlu JWT. Validasi dilakukan via domain (tabel websites).
APP_DEBUG=true, server akan fallback ke website pertama yang aktif — memudahkan testing lokal. Di production (APP_DEBUG=false) domain wajib terdaftar.
/widget/init
Inisialisasi widget: validasi domain, return config (nominal, nama app).
{ "domain": "beritaxyz.com" }
Response 200
{
"status": true,
"data": {
"website_id": 1,
"website_name": "Portal Berita XYZ",
"app_name": "Apresiasi Mitra Promedia",
"amounts": [5000, 10000, 20000, 50000, 100000],
"tax_percent": 11
}
}
/widget/create-transaction
Buat transaksi baru. Transaksi selalu tersimpan ke DB terlebih dahulu, lalu API Xendit dipanggil untuk generate QR code. Jika Xendit key belum valid, transaksi tetap tersimpan dengan qris_url kosong.
{
"website_id": 1,
"amount": 20000,
"payment_method": "gopay",
"donor_name": "John Doe",
"donor_email": "john@email.com",
"article_url": "https://beritaxyz.com/artikel-1",
"article_title": "Judul Artikel",
"author_name": "Nama Penulis"
}
Response 201
{
"status": true,
"code": 201,
"message": "Transaksi berhasil dibuat",
"data": {
"invoice_no": "APR-20260421-A1B2C3D4",
"transaction_id": 12,
"amount": 20000,
"tax_amount": 2200,
"qris_string": "00020101021226...",
"expired_at": "2026-04-21 10:00:00",
"payment_url": "https://apresiasi.test/widget/payment.php?invoice=APR-20260421-A1B2C3D4"
}
}
/widget/check-status/{invoice_no}
Polling status pembayaran, dipanggil setiap 3 detik oleh halaman payment. Auto-expire jika sudah melewati expired_at.
{
"status": true,
"data": {
"invoice_no": "APR-20260421-A1B2C3D4",
"status": "pending",
"amount": 20000,
"donor_name": "John Doe",
"paid_at": null,
"expired_at": "2026-04-21 10:00:00"
}
}
/webhooks/payment
Endpoint callback dari Xendit. Konfigurasi URL ini di dashboard Xendit sebagai Webhook URL.
Verifikasi menggunakan header x-callback-token yang dicocokkan dengan nilai XENDIT_WEBHOOK_TOKEN di .env.
Event yang di-handle
| Event / Status Xendit | Status Internal | Efek |
|---|---|---|
qr.payment | paid | Update status + catat paid_at |
status = COMPLETED / PAID / SUCCEEDED | paid | Update status + catat paid_at |
status = EXPIRED / FAILED | expired / failed | Update status saja |
URL yang perlu dikonfigurasi di Xendit
https://apresiasi.test/api/index.php?route=webhooks/payment
Laporan agregasi transaksi berdasarkan artikel, penulis, atau website. Permission: transactions.view / transactions.export.
/reports
AUTH
List data yang di-group berdasarkan artikel, penulis, atau website.
| Parameter | Tipe | Keterangan |
|---|---|---|
group_by | string | article (default) | author | website |
website_id | integer | Filter per website (admin only) |
author | string | Filter nama penulis (LIKE) |
date_from | date | Filter tanggal mulai (Y-m-d) |
date_to | date | Filter tanggal akhir (Y-m-d) |
page | integer | Nomor halaman (default: 1) |
per_page | integer | Data per halaman (default: 15) |
GET /reports?group_by=author&date_from=2026-04-01&date_to=2026-04-30
{
"status": true,
"data": {
"items": [
{
"author_name": "Ahmad Fauzi Santoso",
"article_count": 3,
"trx_count": 12,
"paid_count": 8,
"total_gross": 80000,
"total_net": 71200,
"total_website": 35600,
"total_platform": 35600
}
],
"total": 1,
"page": 1,
"per_page": 15,
"total_pages": 1
}
}
/reports/summary
AUTH
Ringkasan statistik dari hasil filter aktif (total transaksi, pendapatan, pembagian).
Parameter filter sama dengan GET /reports (kecuali group_by, page, per_page).
GET /reports/summary?date_from=2026-04-01&date_to=2026-04-30
{
"status": true,
"data": {
"total_transactions": 20,
"paid_count": 15,
"pending_count": 3,
"expired_count": 2,
"author_count": 5,
"article_count": 8,
"website_count": 2,
"total_gross": 150000,
"total_tax": 16500,
"total_net": 133500,
"total_website": 66750,
"total_platform": 66750
}
}
/reports/export?export_token={token}&group_by={type}
EXPORT TOKEN
Download CSV laporan. Gunakan export token one-time (POST ke /export-tokens terlebih dahulu).
// 1. Dapatkan export token
POST /export-tokens
{ "endpoint": "reports/export", "params": { "group_by": "author", "date_from": "2026-04-01" } }
// 2. Download CSV
GET /reports/export?export_token=TOKEN&group_by=author&date_from=2026-04-01
Panduan memasang widget apresiasi di website mitra.
Letakkan sebelum tag </body>:
<script src="https://apresiasi.test/widget/apresiasi-widget.js"></script>
Tambahkan class obj-apresiasi-promedia pada element apapun:
<a href="#" class="obj-apresiasi-promedia" data-url="https://web.com/artikel-1" data-title="Judul Artikel" data-author="Nama Penulis"> Beri Apresiasi </a>
| Attribute | Keterangan |
|---|---|
data-url | URL artikel (default: URL halaman saat ini) |
data-title | Judul artikel (default: document.title) |
data-author | Nama penulis |
Website wajib didaftarkan terlebih dahulu oleh admin melalui menu Websites di CMS. Domain yang didaftarkan harus cocok dengan domain tempat widget dipasang (misal beritaxyz.com).
Development / testing: Jika
APP_DEBUG=truedi.envdan domain belum terdaftar, server akan otomatis fallback ke website pertama yang aktif di database. Ini memudahkan pengujian lokal tanpa perlu mendaftarkan domainlocalhostatauapresiasi.test.
Pastikan domain website mitra sudah ditambahkan ke CORS_ALLOWED_ORIGINS di .env server:
[security] CORS_ALLOWED_ORIGINS=https://apresiasi.test,https://beritaxyz.com,https://www.beritaxyz.com
Browser akan memblok request dari origin yang tidak terdaftar. Setiap subdomain perlu didaftarkan secara terpisah.
1. User klik tombol .obj-apresiasi-promedia
↓
2. Widget call POST /widget/init (validasi domain, ambil config)
↓
3. Popup terbuka — Step 1: pilih nominal apresiasi
↓
4. Step 2: pilih metode QRIS + isi nama & email (opsional)
↓
5. Klik "Lanjutkan Pembayaran"
→ Widget call POST /widget/create-transaction
→ Transaksi tersimpan di DB (status: pending)
→ Xendit API dipanggil untuk generate QR string
↓
6. Buka halaman payment.php?invoice=APR-xxx di tab baru
→ Tampil QR code (render dari qr_string Xendit)
→ Countdown timer berjalan
→ Polling GET /widget/check-status/{invoice} tiap 3 detik
↓
7. Setelah scan & bayar:
→ Xendit kirim webhook ke POST /webhooks/payment
→ Status transaksi update ke "paid"
→ Halaman payment auto-refresh → tampil pesan sukses
URL: https://apresiasi.test/widget/payment.php?invoice={invoice_no}
qr_string menggunakan library qrcode.jsexpired_at dari databaseqr_string kosong (Xendit key belum dikonfigurasi), ditampilkan placeholder tekspaid atau expiredUntuk testing end-to-end sebelum Xendit API key tersedia:
APP_DEBUG=true di .envwebsiteshttps://apresiasi.test/widget/index.html (halaman demo artikel)pending dan qris_url kosongTidak diperlukan modifikasi kode apapun — simulasi berjalan otomatis selama APP_DEBUG=true.
https://apresiasi.test/api/index.php?route=webhooks/payment.env:[payment] XENDIT_SECRET_KEY=xnd_production_xxxxx XENDIT_WEBHOOK_TOKEN=your_webhook_token XENDIT_IS_PRODUCTION=true
| Masalah | Penyebab | Solusi |
|---|---|---|
| Widget tidak muncul / tidak bisa diklik | widgetConfig null (init gagal) |
Pastikan domain terdaftar di DB atau APP_DEBUG=true |
| Request diblok browser (CORS error) | Domain tidak ada di whitelist | Tambahkan domain ke CORS_ALLOWED_ORIGINS di .env |
| Halaman payment: Invoice tidak ditemukan | create-transaction gagal sebelum redirect |
Cek network tab browser untuk error dari API |
| QR tidak muncul, placeholder tampil | XENDIT_SECRET_KEY belum diisi / masih dummy |
Normal untuk simulasi. Isi key valid untuk production |
| Transaksi tidak masuk DB | Domain widget/init gagal (403) |
Daftarkan domain di CMS, atau aktifkan APP_DEBUG=true |