เปิดใช้งาน
tangla-manage เป็นหน้าเว็บสำเร็จรูปที่ต่อกับตังค์ละในวง Wi-Fi เดียวกัน ใช้ชื่อผู้ใช้/รหัสผ่านที่ตั้งในแอป (ดูขั้นตอนด้านล่าง) ดู เพิ่ม แก้ไข และลบรายการ บัญชี หมวดหมู่ แท็ก งบประมาณ และรายการประจำได้จากเบราว์เซอร์เลย ไม่ต้องยิง API เอง ส่วนที่เหลือของหน้านี้สำหรับคนที่จะเขียนสคริปต์หรือเครื่องมือของตัวเอง
ถ้าต้องการแค่เพิ่มรายการเดียวจากสิ่งที่ต้องถือเครื่องอยู่แล้ว
— Shortcut บนหน้าโฮม, สแกน QR, แตะ NFC tag — ใช้ลิงก์
tangla://add?… แทนหน้านี้จะง่ายกว่ามาก ไม่ต้องเปิด server,
ไม่ต้องมีโทเค็น, ไม่ต้องยอมรับใบรับรอง และทำงานได้แม้ปิดแอปอยู่
เปิดได้ที่ในแอป ตั้งค่า → การเชื่อมต่อภายนอก →
"รับรายการจากลิงก์ทางลัด" (ฟีเจอร์พรีเมียม, มีรูปแบบลิงก์เต็มในหน้านั้น)
ใช้ HTTP API ในหน้านี้ต่อเมื่อต้องเพิ่มหลายรายการพร้อมกัน (เช่น import จากไฟล์ statement) หรือสั่งจากอุปกรณ์อื่นที่ไม่ใช่เครื่องนี้ — สองอย่างนี้ลิงก์ทางลัดทำไม่ได้
ในตังค์ละ ไปที่ ตั้งค่า → การเชื่อมต่อภายนอก → "API ภายนอก" แล้วเปิด "เปิดใช้งาน API" ถ้ายังไม่เคยตั้ง ชื่อผู้ใช้/รหัสผ่านมาก่อน แอปจะถามให้ตั้งทันที — เพราะไม่มีอะไรใช้ได้เลย ถ้าไม่มีบัญชีสำหรับเข้าสู่ระบบ (ดู การยืนยันตัวตน) กดยกเลิกแล้วมาตั้งทีหลังจาก การ์ดด้านล่างก็ได้ เซิร์ฟเวอร์ยังทำงานต่อ หน้าจอจะแสดง:
-
ที่อยู่เซิร์ฟเวอร์ — ค่าเดียวรูปแบบ
https://<ip>:<port>พอร์ตสุ่มครั้งเดียว แล้วใช้ค่าเดิมทุกครั้งที่เปิด จึงคัดลอกเข้าเครื่องมือแค่ครั้งเดียว ถ้าใช้ขอบเขต LAN บน Android และเครือข่ายรองรับ mDNS จะโชว์https://tangla.local:<port>เพิ่มด้วย — ชื่อคงที่ที่ยังใช้ได้แม้ Wi-Fi เปลี่ยน IP จึงยืนยันใบรับรอง ครั้งเดียวจบ ถ้าเครือข่ายไม่รองรับก็ใช้ IP ตามเดิม - เข้าสู่ระบบผ่านเว็บ — การ์ดตั้ง/เปลี่ยน/ลบ ชื่อผู้ใช้และรหัสผ่าน
เซิร์ฟเวอร์ทำงานเฉพาะตอนเปิดแอปค้างไว้ที่หน้าจอเท่านั้น — หน้าจอมือถือดับหรือสลับไปแอปอื่นเมื่อไหร่ เซิร์ฟเวอร์ก็หยุดตอบทันที ไม่ต้องรอให้ระบบปิดแอปสนิทก่อน (ตั้งใจไม่ให้ทำงานเป็น background service) จะเริ่มใหม่เองตอนเปิดแอปครั้งถัดไปถ้าเคยเปิดใช้งานไว้ ถ้าจะเรียก API แบบไม่มีคนเฝ้าหน้าจอ (เช่น shortcut/automation ที่ตั้งเวลา) ต้องเปิดแอปค้างไว้และกันจอไม่ให้ดับตลอดช่วงนั้นด้วย ตั้งได้ในหน้าจอเดียวกัน ตามด้านล่าง
กันจอไม่ให้ดับ
สวิตช์ "ให้หน้าจอค้างไว้" ทำให้จอไม่ดับตอนเซิร์ฟเวอร์เปิดอยู่ และแอปอยู่หน้าจอ — สลับไปแอปอื่นเมื่อไหร่ จอก็ดับตามปกติ และเซิร์ฟเวอร์หยุดตอบเหมือนเดิม การค้างหน้าจอสิ้นเปลืองแบตเตอรี่ จึงแนะนำให้เสียบสายชาร์จไว้ เมื่อเปิดสวิตช์นี้จะมีอีกสองตัวเลือกให้ตั้ง เพื่อให้ไม่ต้องค้างจอสว่างเต็มที่ตลอดเวลา:
- หรี่หน้าจอเมื่อไม่ได้แตะ — จอยังติดอยู่แต่มืดลง เพื่อประหยัดแบต เลือกได้ 15 วินาที, 30 วินาที (ค่าเริ่มต้น), 1 นาที หรือไม่หรี่เลย แตะที่ไหนก็ได้เพื่อให้กลับมาสว่างเท่าเดิม โดยการแตะครั้งแรกจะไม่กดปุ่มที่อยู่ใต้นิ้ว
- ปล่อยให้จอดับเมื่อไม่มีการใช้งานเว็บ — ถ้าไม่มีคำขอเข้ามาที่เซิร์ฟเวอร์นานเท่าที่ตั้งไว้ แอปจะเลิกค้างหน้าจอ และปล่อยให้ดับตามปกติ (แปลว่าเซิร์ฟเวอร์หยุดตอบด้วย) เลือกได้ 5, 15 (ค่าเริ่มต้น), 30, 60 นาที หรือไม่ปล่อยเลย
HTTPS กับใบรับรอง self-signed
เซิร์ฟเวอร์ใช้ HTTPS และใบรับรองเป็นแบบ self-signed (สร้างในเครื่องตอนใช้ครั้งแรก) ดังนั้น:
-
เบราว์เซอร์จะเตือนว่า "ไม่น่าเชื่อถือ" — กด
ขั้นสูง → ไปต่อ ครั้งเดียวต่อ
IP:PORTถ้า Wi-Fi เปลี่ยนแล้ว IP เปลี่ยนไป ต้องยืนยันใหม่ curlต้องใส่-k(insecure)- ควรใช้เฉพาะเครือข่ายบ้าน/ในวงที่เชื่อถือได้
แอปแสดง ลายนิ้วมือ SHA-256 ของใบรับรองไว้ในหน้า
"API ภายนอก" — เปิดที่อยู่เครื่องโดยตรงในแท็บใหม่ แล้วเทียบกับ
ค่า SHA-256 ในรายละเอียดใบรับรองที่เบราว์เซอร์แสดงตรงนั้น
เพื่อยืนยันว่าต่อถึงเครื่องที่ถูกต้อง ไม่มีตัวกลางในวง LAN สวม
ใบรับรองปลอม (ไม่ใช่ใบรับรองของหน้า tangla-manage เอง เพราะเป็น
ใบรับรองของเว็บ คนละใบกัน) ค่าเดียวกันนี้เรียกผ่าน
GET /v1/server/fingerprint ได้ (ไม่ต้องแนบโทเค็น —
ต้องเช็คได้ก่อนล็อกอินเพื่อยืนยันว่าไม่มีตัวกลางดักอยู่)
ขอบเขตการเข้าถึง
ตั้งในหน้าจอเดียวกัน เซิร์ฟเวอร์จะสร้างใหม่เมื่อเปลี่ยนค่า
| ขอบเขต | Bind ที่ | เข้าถึงได้จาก |
|---|---|---|
| เฉพาะเครื่องนี้ | 127.0.0.1 เท่านั้น |
เครื่องมือในเครื่องนี้เท่านั้น —
adb reverse
หรือแอป/สคริปต์อัตโนมัติในเครื่อง
อุปกรณ์อื่นเข้าไม่ได้แม้มีโทเค็น
|
| ทั้งวง Wi-Fi/LAN | 0.0.0.0 |
อุปกรณ์ใดก็ได้ในวง Wi-Fi/LAN เดียวกันที่มีโทเค็นที่ถูกต้อง |
ขอบเขต "เฉพาะเครื่องนี้" ที่อยู่ที่แอปแสดงคือ
127.0.0.1 ส่วน "ทั้งวง Wi-Fi/LAN" จะเป็น IP ของ Wi-Fi
เครื่องนั้น
การยืนยันตัวตน
มีโทเค็นแบบเดียว: POST /v1/auth/login
ด้วยชื่อผู้ใช้/รหัสผ่านที่ตั้งในแอป จะได้ bearer JWT
(อายุ 60 นาที ไม่มี refresh — หมดอายุแล้วก็ login ใหม่)
ทุก request ที่เหลือ รวมถึง /v1/health
ต้องแนบโทเค็นนี้:
Authorization: Bearer <token>
ไม่มีโทเค็นหรือโทเค็นผิด → 401.
POST /v1/auth/login เองเรียกได้โดยไม่ต้องมีโทเค็น
(เพราะเป็นทางเดียวที่จะได้โทเค็นมา) แต่ถ้ายังไม่เคยตั้ง
ชื่อผู้ใช้/รหัสผ่านในแอป จะได้
403 {"error":"Web sign-in is not configured"}
รายการ route ทั้งหมด
รายการ route ทั้งหมดที่เข้าถึงได้หลัง login (ดู การยืนยันตัวตน) ตั้ง ชื่อผู้ใช้ + รหัสผ่าน ได้ที่ ตั้งค่า → การเชื่อมต่อภายนอก → "API ภายนอก" (การ์ด "เข้าสู่ระบบผ่านเว็บ") JWT ที่ได้ใช้กับขอบเขต LAN ได้ด้วย เพราะสร้างจากรหัสผ่านของคุณเอง และมีวันหมดอายุ จึงปลอดภัยกว่าโทเค็นคงที่ หน้าเว็บคู่กันคือ tangla-manage.meowmeow-studio.com
POST /v1/auth/login body
{"username","password"} →
{"token","tokenType":"Bearer","expiresIn":3600}
เป็น HS256 JWT อายุ 60 นาที ไม่มี refresh — เจอ
401 ให้ login ใหม่ 403
ถ้ายังไม่ตั้งชื่อผู้ใช้/รหัสผ่าน 401
ถ้าชื่อผู้ใช้/รหัสผ่านผิด 429 พร้อม
Retry-After หลังผิด 5 ครั้ง
การเปลี่ยนหรือลบชื่อผู้ใช้/รหัสผ่านทำให้ JWT ทุกตัวใช้ไม่ได้
หลังเซิร์ฟเวอร์รีสตาร์ทครั้งถัดไป
ส่ง Authorization: Bearer <jwt> ไปที่:
| Route | หมายเหตุ |
|---|---|
GET /v1/entries |
?from=&to= (epoch ms) หรือ
?cycle= หรือ ?limit=
(ค่าเริ่มต้น 200 สูงสุด 1000) →
{"entries":[…],"count":N} ทุก entry มี
tags:[id]
|
POST /v1/entries |
สร้างรายการเดียว body เหมือนสมาชิก entries[]
หนึ่งตัว (มี autoCreate,
idempotencyKey, tagIds)
|
PUT /v1/entries/<id> |
แทนที่ทั้งรายการ — ฟิลด์ที่ไม่ส่งจะถูก
ล้าง (title → "",
note → null) ส่งทั้งรายการ
timestamp รูปใบเสร็จ และที่มาแบบ
ประจำ/ผ่อนถูกเก็บไว้ 404 ถ้าไม่มี id นั้น
|
DELETE /v1/entries/<id> |
ย้อนผลต่อยอดเงิน ลบเฉพาะรายการนั้นของแผนผ่อน
404 ถ้าไม่มี id นั้น
409 ถ้าเป็นรายการรับ/จ่ายของบัญชีเงินกู้
(ยอดหนี้หรือการปรับยอด ซึ่งแก้ได้ในแอปเท่านั้น)
|
GET /v1/categories |
{"income":[…],"expense":[…]}
หมวดที่ซ่อนไว้รวมมาด้วย isHidden: true
|
POST /v1/categories |
{name, type, colorIndex?, icon?}
(type = income/expense) ชื่อซ้ำในประเภท
เดียวกัน → 409
|
PUT /v1/categories/<id> |
merge: name/colorIndex/icon/isHidden/sortOrder
เปลี่ยน type ไม่ได้ เปลี่ยนชื่อ cascade ไป
ทุก entry/กฎ/งบ 404/409
|
DELETE /v1/categories/<id> |
409 พร้อม usage ถ้ายังถูกใช้
อยู่ ไม่งั้นลบ
|
POST /v1/accounts |
{name, type?, includeInTotal?, includeInNetWorth?, note?}
ยอดเริ่ม 0 เสมอ (ไม่มี route ปรับยอด — ทำในแอป) ชื่อซ้ำ
→ 409
|
PUT /v1/accounts/<id> |
merge: name/type/includeInTotal/includeInNetWorth/note/isHidden
balance และเงื่อนไขกู้/เป้าหมายถูกเก็บไว้
บัญชีกู้ หรือเปลี่ยน type เป็น/จาก loan →
400
|
DELETE /v1/accounts/<id> |
409 ถ้ามี entry อ้างถึง (ซ่อนแทน) ไม่งั้น
soft-delete
|
GET /v1/tags |
ทุกแท็ก →
[{id,name,tagType,colorIndex,sortOrder}]
|
POST /v1/tags |
{name, colorIndex?} เป็น
tagType:"custom" เสมอ ชื่อซ้ำ →
409
|
PUT /v1/tags/<id> |
merge name/colorIndex/sortOrder
404/409
|
DELETE /v1/tags/<id> |
ลบแท็กและทุกแถวที่ใช้แท็กนั้น แท็ก need/want ลบไม่ได้
(400)
|
GET /v1/budgets |
?cycle=<ms> (ค่าเริ่มต้นตอนนี้) → งบ
หมวดหมู่ของรอบนั้น
{cycle:{key,start,end}, budgets:[…]}
|
PUT /v1/budgets |
upsert งบหนึ่งหมวด:
{category, monthlyLimit, cycle?, muted?}
monthlyLimit ≥ 0, category
ต้องเป็นหมวดรายจ่ายที่มีอยู่
|
DELETE /v1/budgets/<id> |
404 ถ้าไม่มีแถวนั้น |
GET /v1/recurring |
ทุกกฎรายการประจำ เรียง nextRun ใกล้สุดก่อน
แต่ละอันมี linkedLoan — กฎที่ผูกบัญชีกู้แก้
ที่นี่ไม่ได้
|
POST /v1/recurring |
สร้างกฎประจำแบบธรรมดา body เหมือน entry +
frequency (daily/weekly/monthly/yearly) +
dayOfWeek (1–7 จำเป็นเมื่อ weekly) /
dayOfMonth (1–28 จำเป็นเมื่อ monthly และ
yearly) / monthOfYear (1–12 จำเป็นเมื่อ
yearly) และไม่บังคับ occurrenceLimit
(1–1000), postLeadDays (0–31),
reminderLeadDays (0–30) ถ้าขาดฟิลด์ตารางหรือ
ค่าอยู่นอกช่วงจะได้ 400
nextRun คำนวณจากตารางถ้าไม่ส่ง
|
PUT /v1/recurring/<id> |
merge nextRun คำนวณใหม่เมื่อมีฟิลด์ตาราง
ใน body 400 ถ้าเป็นกฎผูกกู้
|
DELETE /v1/recurring/<id> |
400 ถ้าเป็นกฎผูกกู้ 404 ถ้าไม่มี |
GET /v1/invest/prices |
บัญชีลงทุนที่ยังถืออยู่ พร้อมราคาล่าสุดที่บันทึกไว้ของแต่ละตัว
{accounts:[{id,name,currency,holdings:[{ticker,shares,price,priceUpdatedAt,stale}]}]}
ใช้ได้เฉพาะแบบ Premium (ถ้าไม่ใช่ จะได้ 403
premium_required)
|
PUT /v1/invest/prices |
{accountId, prices:{TICKER:price,…}, asOf?}
ตั้งราคาหลายตัวพร้อมกัน asOf คือวันที่ของราคา
(YYYY-MM-DD ไม่ใช่อนาคต) ตัวที่ไม่ได้ถืออยู่หรือราคาไม่เป็นบวก
→ 400 ไม่บันทึกอะไรเลย บัญชีไม่มี →
404 เฉพาะแบบ Premium
|
GET /v1/server/fingerprint |
ลายนิ้วมือ SHA-256 ของใบรับรอง →
{"sha256":"AB:CD:…"}
|
GET /v1/theme |
ชุดสีที่ผู้ใช้เลือกในแอปและสีของหมวดหมู่/แท็ก →
{"flavor":"warm","categoryPalette":[{"bg":"#f5e7e3","fg":"#be4a42"},…]}
(อ้างด้วย colorIndex)
|
tagIds —
POST /v1/entries, PUT /v1/entries/<id>
และทุก batch item รับ tagIds: [<int>…]: ไม่ส่ง
= ไม่แตะแท็กเดิม, [] = ล้างแท็ก, ลิสต์ = แทนที่ทั้งชุด
id ที่ไม่รู้จัก → 400
บัญชีชนิด loan ถูกปฏิเสธ (400)
เมื่อใช้เป็นบัญชี income/expense หรือเป็นบัญชีต้นทางของ
transfer — เป็นได้แค่ปลายทางของ transfer (การชำระคืน)
GET /v1/health
ตรวจว่าเซิร์ฟเวอร์ทำงาน คืน
{ "status": "ok" }
POST /v1/entries/batch
สร้างรายการทีเดียวได้หลายรายการ ถ้าจะเพิ่มแค่รายการเดียว
POST /v1/entries
(ดูรายการ route ทั้งหมด) ง่ายกว่า
{
"autoCreate": false,
"entries": [
{ "type": "expense", "amount": 60, "account": "SCB", "category": "อาหาร" },
{ "type": "transfer", "amount": 500, "account": "SCB", "toAccount": "Wallet" }
]
}
entries ต้องเป็น array ที่ไม่ว่าง สูงสุด
100 รายการ แต่ละรายการ:
| ฟิลด์ | บังคับ | หมายเหตุ |
|---|---|---|
type |
ใช่ |
income, expense หรือ
transfer (ไม่สนตัวพิมพ์)
|
amount |
ใช่ | ตัวเลข ≥ 0 |
account |
ใช่ | id (ตัวเลข) หรือชื่อบัญชีที่มีอยู่ — เป็นบัญชี "ต้นทาง" ของ transfer |
toAccount |
เฉพาะ transfer | id หรือชื่อบัญชีปลายทาง ต้องต่างจาก account |
category |
เฉพาะ income/expense | ชื่อหมวดหมู่ของประเภทนั้น |
title |
ไม่ | ข้อความอิสระ ค่าเริ่มต้น "" |
note |
ไม่ | ข้อความอิสระ |
timestamp |
ไม่ | epoch milliseconds ค่าเริ่มต้นคือตอนนี้ |
idempotencyKey |
ไม่ |
ส่ง key เดิมซ้ำ → ได้รายการเดิมกลับ
("idempotent": true) ไม่สร้างใหม่
รวมถึงกรณีส่งซ้ำระหว่างที่คำขอแรกยังเขียนไม่เสร็จ
ถ้ารายการนั้นถูกลบไปแล้ว การส่งซ้ำจะได้
400 ให้ใช้ key ใหม่ เก็บใน
หน่วยความจำเท่านั้น (สูงสุด 1000 key ล้างเมื่อรีสตาร์ท)
|
tagIds |
ไม่ |
array ของ id แท็ก (GET /v1/tags) —
[] = ไม่มีแท็ก, id ที่ไม่รู้จัก → item ล้ม
|
การสร้างแบบ batch เป็นแบบ best-effort — ตรวจและสร้างแต่ละรายการแยกกัน รายการที่ผิดไม่ทำให้รายการอื่นล้ม เป็น loop ธรรมดา ไม่ใช่ database transaction
สร้างบัญชี/หมวดหมู่อัตโนมัติ
โดยปกติชื่อ account/toAccount หรือ
category ที่ไม่มีอยู่จะเป็น error ของรายการนั้น
ใส่ "autoCreate": true ที่ระดับบนสุด
ของ request (มีผลทั้ง batch) เพื่อสร้างสิ่งที่ขาดแทน:
- บัญชี/toAccount ที่ส่งมาเป็นชื่อ → สร้างบัญชีใหม่ชื่อนั้น ประเภทเป็นชนิดทรัพย์สินแรกของคุณ (ปกติคือเงินสด) ยอด 0 — ส่งมาเป็น id ไม่มีการสร้างอัตโนมัติ id ที่ไม่รู้จักถูกปฏิเสธเสมอ
- หมวดหมู่ → สร้างหมวดหมู่ใหม่ตามประเภทของรายการ สีคำนวณจากชื่อ ไอคอนทั่วไป
บัญชี/หมวดหมู่ที่เพิ่งสร้าง รายการถัด ๆ ไปใน batch เดียวกันเห็นทันที
ผลลัพธ์
201 เมื่อทุกรายการถูกสร้างใหม่หมด
200 กรณีอื่น (บางรายการล้ม หรือเป็น idempotent replay):
{
"created": 1,
"idempotent": 0,
"failed": 1,
"results": [
{ "success": true, "entry": { "id": 107, "title": "", "amount": 60.0,
"type": "EXPENSE", "category": "อาหาร", "account": 1, "toAccount": null,
"note": null, "timestamp": 1786806400000, "currency": "THB", "tags": [] } },
{ "success": false, "error": "\"category\" must match an existing expense category" }
]
}
currency เป็นสกุลเงินหลักของแอปเสมอ
(ตั้งค่า → "สกุลเงินหลัก") ไม่มีการตั้งต่อ request
บัญชีชนิด 'loan' ที่ใช้เป็นบัญชีรายรับ/รายจ่าย หรือ
เป็นบัญชีต้นทางของการโอน จะล้มเป็นราย item ด้วย
"A loan account can only be the destination of a transfer …"
(บัญชีกู้ยืมเป็นได้แค่ปลายทางของการโอนเท่านั้น)
error ทั้ง request: 400 ถ้า
entries หาย/ว่าง/ไม่ใช่ array, เกิน 100 รายการ หรือ
body ไม่ใช่ JSON ที่ถูกต้อง; 413 ถ้า body เกิน
1 MiB
การอ่านข้อมูล
เอนด์พอยต์นี้เหมาะกับตอนดึงยอดไปใช้ต่อในที่อื่น — สคริปต์, วิดเจ็ตของแอปอื่น (KWGT/Zooper), ตัวแปรใน Tasker — ไม่ใช่ไว้ดูเฉยๆ ถ้าแค่อยากดูยอดบนมือถือเครื่องนี้เอง วิดเจ็ต Balance/Budget ของตังค์ละทำได้ดีกว่ามาก เพราะอ่านฐานข้อมูลตรง ในเครื่อง ไม่ผ่าน HTTP เลย จึงยังโชว์ยอดล่าสุดได้แม้จอดับหรือปิด สวิตช์ API ภายนอกไว้
อยากดูจากอุปกรณ์อื่นในวง LAN ก็เปิดหน้าเว็บ tangla-manage ตรงๆ แทนการเขียนสคริปต์ดึงเป็นระยะ — เพราะไม่ว่าจะ scope ไหน เซิร์ฟเวอร์ก็หยุดทำงานทันทีที่มือถือจอดับเหมือนกัน การเปิด "เปิดใช้งาน API" ค้างไว้เป็นแค่ค่าที่บันทึกไว้ ไม่ได้แปลว่าเรียกได้ตลอดเวลา
เปิดใช้งาน API + ตั้งชื่อผู้ใช้/รหัสผ่านแล้วเรียกได้เลย
— login แล้วเอา JWT มา GET สองเอนด์พอยต์นี้ได้ทันที
GET /v1/summary
?cycle=<epoch-ms>
เลือกรอบบิลที่ครอบเวลานั้น (ค่าเริ่มต้น: ตอนนี้)
การจ่ายบัตรเครดิตนับเป็นรายจ่าย ตรงกับหน้าโฮม
{
"cycle": { "key": "2026-09", "start": 1788220800000, "end": 1790812799999 },
"income": 15000.0,
"expense": 4230.5,
"net": 10769.5,
"netBalance": 88123.0
}
netBalance = ผลรวมยอดของบัญชีที่ "นับรวมในยอดรวม" ทุกบัญชี
GET /v1/accounts
ทุกบัญชีพร้อมยอดคงเหลือปัจจุบัน
[
{ "id": 1, "name": "เงินสด", "type": "cash", "balance": 1234.0,
"currency": "THB", "includeInTotal": true, "includeInNetWorth": true,
"isHidden": false, "note": null }
]
ตัวอย่าง curl
HOST="<ที่อยู่จากแอป เช่น https://192.168.1.117:53214>"
USERNAME="<ชื่อผู้ใช้ที่ตั้งไว้ในแอป>"
PASSWORD="<รหัสผ่านที่ตั้งไว้ในแอป>"
# login ครั้งเดียว ใช้โทเค็นซ้ำได้นาน 60 นาที (-k เพราะใบรับรองเป็น self-signed)
TOKEN=$(curl -sk -X POST "$HOST/v1/auth/login" \
-d "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD\"}" \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["token"])')
# ตรวจสถานะ
curl -k -H "Authorization: Bearer $TOKEN" "$HOST/v1/health"
# รายจ่ายหนึ่งรายการ อ้างบัญชีด้วยชื่อ
curl -k -X POST "$HOST/v1/entries/batch" \
-H "Authorization: Bearer $TOKEN" \
-d '{"entries":[{"type":"expense","amount":60,"account":"SCB","category":"อาหาร","title":"กาแฟ"}]}'
# โอนหนึ่งรายการ
curl -k -X POST "$HOST/v1/entries/batch" \
-H "Authorization: Bearer $TOKEN" \
-d '{"entries":[{"type":"transfer","amount":500,"account":"SCB","toAccount":"True Wallet"}]}'
# สร้างบัญชี/หมวดหมู่ถ้ายังไม่มี และกันยิงซ้ำด้วย key
curl -k -X POST "$HOST/v1/entries/batch" \
-H "Authorization: Bearer $TOKEN" \
-d '{"autoCreate":true,"entries":[
{"type":"expense","amount":1000,"account":"กองทุนฉุกเฉิน",
"category":"ลงทุน","idempotencyKey":"sep-invest-2026-09"}
]}'
# สรุปของรอบนี้
curl -k -H "Authorization: Bearer $TOKEN" "$HOST/v1/summary"
หาชื่อบัญชีและหมวดหมู่
ชื่อบัญชีและหมวดหมู่คือสิ่งที่แอปแสดงตรง ๆ
(ตั้งค่า → "บัญชีทรัพย์สิน" และ "หมวดหมู่") ส่วน id (ตัวเลข)
ก็ใช้ได้แต่ไม่แสดงใน UI — เรียก
GET /v1/accounts / GET /v1/categories
หรือส่ง batch หนึ่งรายการที่ใส่หมวดหมู่ผิดตั้งใจ โดยปิด
autoCreate: ค่าใน results[0].error
บอกว่า id ถูกต้องไหม
("No account with id <n>" เทียบกับ
"category" must match …) และไม่มีอะไรถูกสร้าง
ควรรู้
- เซิร์ฟเวอร์เข้าถึงได้เฉพาะตอนแอปเปิดอยู่ ไม่มีโหมดเบื้องหลัง
- ทุกการสร้าง/แก้ไข/ลบ ผ่าน write core เดียวกับหน้าเพิ่มรายการปกติ ยอดเงินและผลข้างเคียงจึงถูกต้อง
- บัญชีแยกสกุลเงินได้เดียวทั้งเล่ม ไม่มีสกุลเงินต่อรายการ
- ดูเพิ่มที่ นโยบายความเป็นส่วนตัว ว่าเซิร์ฟเวอร์นี้เปิดเผยและไม่เปิดเผยอะไรบ้าง