API · OAuth 2.0

Sambung dengan SAPRO

Beri pengguna sistem anda satu butang: pemilik syarikat log masuk ke SAPRO, pilih syarikat, tekan Benarkan, dan sistem anda boleh terus hantar jurnal dan baca laporan. Tiada token untuk disalin, tiada langkah manual.

Sambung dengan SAPROContoh butang dalam sistem anda · Lihat bagaimana PVSWIM menggunakannya
1

Daftar aplikasi

Hubungi SAPRO dengan nama aplikasi, logo, alamat kembali (redirect URI) dan akses yang perlu. Anda terima client_id dan client_secret.

2

Tambah butang

Butang menghantar pemilik ke /oauth/authorize. SAPRO menghantar mereka kembali dengan kod, dan pelayan anda menukarnya kepada token.

3

Panggil API

Guna token sebagai Bearer pada https://sapro.my/api/v1. Token terikat kepada satu syarikat dan akses yang dibenarkan pemilik.

Aliran sambungan

OAuth 2.0 authorization code (RFC 6749), dengan PKCE S256 (RFC 7636) yang disyorkan. Semua panggilan token dari pelayan anda: client_secret tidak boleh berada dalam app telefon atau pelayar.

1. Hantar pemilik ke SAPRO

GET https://sapro.my/oauth/authorize
    ?response_type=code
    &client_id=sapro_xxxxxxxxxxxxxxxxxxxxxxxx
    &redirect_uri=https://sekolah.contoh.my/sapro/callback
    &scope=accounts:read branches:read journals:write
    &state=RAWAK_DISIMPAN_DALAM_SESI
    &code_challenge=BASE64URL(SHA256(code_verifier))
    &code_challenge_method=S256

2. Pemilik kembali ke sistem anda

https://sekolah.contoh.my/sapro/callback?code=KOD_SEKALI_GUNA&state=RAWAK_DISIMPAN_DALAM_SESI
https://sekolah.contoh.my/sapro/callback?error=access_denied&state=RAWAK_DISIMPAN_DALAM_SESI

Kod sah 10 minit dan hanya sekali. Ralat lain yang mungkin: invalid_scope, invalid_request, unsupported_response_type.

3. Tukar kod kepada token (dari pelayan)

curl -X POST https://sapro.my/oauth/token \
  -u "CLIENT_ID:CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code=KOD_SEKALI_GUNA \
  -d redirect_uri=https://sekolah.contoh.my/sapro/callback \
  -d code_verifier=CODE_VERIFIER_ASAL
{
  "access_token": "12|q8mW...",
  "token_type": "Bearer",
  "scope": "accounts:read branches:read journals:write",
  "company": { "id": 42, "name": "Splash Swim Academy Sdn Bhd" },
  "api_base_url": "https://sapro.my/api/v1"
}

client_id dan client_secret juga boleh dihantar dalam badan borang. Simpan token secara encrypted. Ia tidak luput; ia berhenti apabila pemilik memutuskan sambungan (app SAPRO → Tetapan → Integrasi), apabila anda memulangkannya, atau apabila pemilik menyambung semula (token lama diganti).

4. Putuskan dari sistem anda

curl -X POST https://sapro.my/oauth/revoke -u "CLIENT_ID:CLIENT_SECRET" -d token=ACCESS_TOKEN

Skop (akses)

Pemilik melihat senarai ini dalam Bahasa Melayu sebelum menekan Benarkan. Minta yang perlu sahaja.

SkopApa yang dibuka
accounts:readLihat carta akaun
branches:readLihat senarai cawangan
journals:readLihat jurnal manual
journals:writeTambah, ubah dan padam jurnal manual
reports:readLihat laporan kewangan (untung rugi, kunci kira-kira, lejar)
loginBuka SAPRO dengan satu klik, untuk pengguna syarikat yang e-melnya sama

Token juga sentiasa boleh memanggil GET /me (pengguna dan syarikat yang bersambung).

API

Asas: https://sapro.my/api/v1. Hantar Authorization: Bearer TOKEN dan Accept: application/json. Jumlah wang dalam ringgit (2 tempat perpuluhan), tarikh YYYY-MM-DD.

Kaedah & laluanSkopKeterangan
GET /mesemuaPengguna, syarikat dan langganan.
GET /accountsaccounts:readCarta akaun (id, nama, kod, jenis). Petakan akaun anda kepada id di sini.
GET /accounts/{id}, GET /accounts/typesaccounts:readSatu akaun; jenis akaun.
GET /branchesbranches:readCawangan syarikat.
GET /journalsjournals:read / writeJurnal manual. Penapis: external_ref, from, to, search, per_page.
GET /journals/{id}journals:read / writeSatu jurnal dengan barisnya.
POST /journalsjournals:writeJurnal baharu. Debit mesti sama dengan kredit.
PUT /journals/{id}journals:writeGanti jurnal (badan yang sama seperti POST).
DELETE /journals/{id}journals:writePadam jurnal.
GET /reports/yearsreports:readTahun kewangan yang ada.
GET /reports/profit-lossreports:readUntung rugi. year, atau from + to.
GET /reports/balance-sheet, /trial-balancereports:readKunci kira-kira; imbangan duga.
GET /reports/general-ledgerreports:readLejar satu akaun: account_id, dan tempoh.
GET /reports/{laporan}reports:readJuga cash-flow, receivables-aging, sales-by-customer, sst-summary, day-book dan lain-lain. Tambah format=pdf atau csv.
POST /integrations/login-linksloginPautan sekali guna untuk buka SAPRO terus, sudah log masuk. email pengguna anda, path (pilihan).

Hantar jurnal

POST /api/v1/journals
X-Branch-Id: 7            (pilihan: cawangan dari GET /branches)

{
  "date": "2026-10-01",
  "reference_number": "KELASPRO 2026-10-01",
  "external_ref": "KP:s12:sales:2026-10-01:7",
  "remark": "Yuran diinvois hari ini",
  "lines": [
    { "account_id": 1201, "description": "Yuran diinvois", "debit": 1250.00 },
    { "account_id": 4101, "description": "Yuran kelas", "credit": 1250.00 }
  ]
}

Elak catatan berganda dengan external_ref (unik dalam syarikat, sehingga 120 aksara). Hantar semula rujukan yang sama menjawab 409 dengan jurnal sedia ada dalam data, jadi cuba semula selepas rangkaian terputus adalah selamat. Untuk membetulkan angka, cari dengan GET /journals?external_ref=... dan PUT.

Contoh (PHP, Laravel)

$sapro = Http::withToken($token)->acceptJson()->baseUrl('https://sapro.my/api/v1');

$accounts = $sapro->get('accounts')->json('data');
$response = $sapro->post('journals', $journal);

if ($response->status() === 409) {
    $existing = $response->json('data');   // sudah dihantar sebelum ini
}

Buka SAPRO dengan satu klik

Dengan skop login, sistem anda boleh meletakkan butang "Buka SAPRO" yang membawa pengguna terus masuk, tanpa kata laluan. Pelayan anda meminta pautan untuk e-mel pengguna yang menekan butang; SAPRO hanya memberi pautan jika e-mel itu milik pengguna syarikat yang bersambung (pemilik atau ahli pasukan). Pautan berfungsi sekali, dalam 2 minit. Buka pautan itu terus (redirect); jangan simpan.

POST /api/v1/integrations/login-links
{ "email": "pengguna@sekolah.my", "path": "/reports/profit-loss" }

201  { "data": { "url": "https://sapro.my/app/#/sso?code=...", "expires_at": "..." } }
404  { "code": "no_user" }          e-mel itu bukan pengguna syarikat ini
403  { "code": "integration_scope" } sambungan tanpa skop login: minta pemilik sambung semula

path ialah halaman dalam app SAPRO, contohnya /reports/profit-loss, /reports/sst-summary atau /journals/{id} (jurnal yang anda hantar).

Ralat

StatusMaksudApa perlu dibuat
401Token tidak sah: pemilik memutuskan sambungan, atau token telah diganti.Tunjuk "Sambung dengan SAPRO" semula.
402 subscription_requiredLangganan SAPRO syarikat telah tamat. Bacaan masih berfungsi.Beritahu pengguna; cuba semula kemudian.
403 integration_scopePemilik tidak memberi akses ini.Minta skop yang betul semasa menyambung.
403 integration_app_stoppedAplikasi anda dihentikan oleh SAPRO.Hubungi SAPRO.
409 duplicateexternal_ref sudah digunakan.Guna jurnal dalam data.
422Data tidak sah; butiran dalam errors.Betulkan dan hantar semula.
429Terlalu banyak panggilan (120 seminit bagi setiap sambungan).Tunggu dan cuba semula.

Mesej ralat dalam message mengikut bahasa Accept-Language (ms atau en).