رابط برنامهنویسی¶
مستند تعاملی روی /docs استقرار خودت است؛ استقرار مرجع روی
kasbbook.nyxon.tech/docs است. این صفحه دربارهٔ قراردادهایی است
که شمای OpenAPI نمیتواند بیانشان کند.
مسیر پایه: /api/v1. بررسی سلامت بیرون از آن است، روی /healthz و /readyz.
قراردادها¶
پول رشته است¶
هرگز عدد JSON نیست. JSON یک نوع عددی دارد و آن هم اعشاری ممیز شناور است؛
12500000.15 از این رفتوبرگشت سالم بیرون نمیآید. سمت خودت با یک نوع دهدهی
تجزیهاش کن — Decimal در پایتون، BigDecimal در جاوا، یک کتابخانهٔ decimal در
جاوااسکریپت. آن را به Number تبدیل نکن.
تاریخها ISOاند، دورهها جلالی¶
occurred_on برابر 2026-08-25 است. اما دورهٔ گزارش 1403-05 است — مرداد
۱۴۰۳ — که پیش از اجرای کوئری به بازهٔ میلادی تبدیل میشود.
| دوره | یعنی |
|---|---|
1403-05 |
یک ماه جلالی |
1403 |
یک سال جلالی |
week |
همین هفته |
| (نبودنش) | همهچیز |
خطاها¶
خطاهای دامنه وضعیت خودشان را دارند: ۴۰۴ پیدا نشد، ۴۰۳ رد شد، ۴۲۲ نامعتبر،
۴۰۹ تعارض، ۴۰۱ احراز نشده، ۴۲۹ محدودیت نرخ (با هدر Retry-After به ثانیه).
مبلغ ۱۰ به توان ۲۴ یا بیشتر ۴۲۲ است. خطاهای اعتبارسنجی از Pydantic
میآیند، با شکل خودش و نام فیلد مقصر.
استثناهای مدیریتنشده همیشه {"detail": "something went wrong on our side"} با
وضعیت ۵۰۰اند. خطای واقعی در لاگ است، هرگز در پاسخ.
«پیدا نشد» یعنی «مال تو نیست»¶
درخواست دفتری که عضوش نیستی ۴۰۴ برمیگرداند، نه ۴۰۳. باطلکردن کلید API دیگری هم ۴۰۴ میدهد. این عمدی است: ۴۰۳ تأیید میکرد که آن شناسه وجود دارد.
گرفتن کلید بدون خارجشدن از تلگرام¶
حساب ← 🔌 API یکی صادر میکند. یک بار نشان داده میشود، پشت یک لمس پنهان، و دیگر هرگز: فقط خلاصهٔ SHA-256 ذخیره میشود، و همین است که یک دیتابیس درزکرده را بیارزش میکند. صدور کلید تازه کلید قبلی را باطل میکند، پس هیچوقت کلید دومی نیست که کسی یادش نباشد ساخته.
احراز هویت¶
یا توکن حامل، یا کلید API. هر دو به یک حساب میرسند، اما مدیریت خود حساب فقط با توکن حامل یک آدم ممکن است — کلیدها، نمایه، راه تماس، رمز، نشستها، بستن حساب و اتصال پیامرسان به کلید API پاسخ ۴۰۳ میدهند.
ثبتنام و ورود¶
curl -X POST /api/v1/auth/register \
-H 'Content-Type: application/json' \
-d '{"display_name":"عماد","email":"you@example.com","password":"at-least-8"}'
ایمیل یا تلفن لازم است — حسابی که هیچ راهی برای رسیدن به صاحبش نداشته باشد قابل بازیابی نیست.
ماندن در حساب¶
توکن تازه را ذخیره کن و قبلی را دور بینداز. هر تازهسازی میچرخد. ارائهٔ توکن مصرفشده کل خانواده را باطل میکند و نشست را بیرون میاندازد — این تشخیص سرقت است که کار میکند، نه ایراد. امنیت را ببین.
خود حساب¶
PATCH /auth/me |
نام نمایشی، منطقهٔ زمانی، زبان |
PUT /auth/me/contact |
ایمیل یا تلفنی که حساب با آن پیدا و بازیابی میشود |
PUT /auth/me/password |
تعیین یا تغییرش — همهٔ نشستها را میبندد، از جمله نشست خودت |
تغییر رمز همهجا را بیرون میاندازد — هم توکنهای تازهسازی و هم توکنهای دسترسی صادرشده، پس همان حاملی که با آن درخواست دادی هم از کار میافتد. کسی که از ترس درز اطلاعات این کار را میکند دقیقاً همین را انتظار دارد. بعدش دوباره وارد شو.
کلید API دستنخورده میماند: مال یک برنامه است، و کار شبانه نباید ساعت سه بامداد بایستد چون آدمی رمزش را عوض کرده.
نشانیای که حساب دیگری از قبل دارد با ۴۲۲ رد میشود — پیامی که تأیید نمیکند آن نشانی ثبت شده است.
GET /auth/me/deletion-preview |
بستن حساب چه چیزی را نابود میکند و چه چیزی جلویش را میگیرد |
DELETE /auth/me |
میبنددش — برگشتناپذیر، بدون مهلت |
DELETE /auth/me وقتی حساب رمز دارد رمز میخواهد؛ حسابی که رمز ندارد فقط با
توکن حامل اثبات میشود. اگر دفتر مشترکی مانع باشد ۴۲۲ برمیگرداند. شناسهای هم
نمیگیرد: یک توکن فقط میتواند حساب خودش را ببندد.
POST /auth/logout |
این نشست را میبندد |
POST /auth/logout-everywhere |
همه را میبندد |
GET /auth/sessions |
این حساب کجاها وارد است |
کلید برای برنامهها¶
curl -X POST /api/v1/auth/api-keys -H "Authorization: Bearer $T" \
-d '{"name":"nightly export","expires_in_days":365}'
پاسخ تنها جایی است که کلید ظاهر میشود. فهرست کلیدها پیشوند را برمیگرداند، نه کلید را.
دفترها و تراکنشها¶
GET /books |
دفترهایی که این حساب میبیند |
POST /books |
{"name":"مغازه","type":"business","currency":"IRR"} |
GET /books/{id}/members · POST · DELETE /{user_id} |
عضویت |
GET /books/{id}/transactions |
?since=&until=&page=&per_page=، تازهترین اول |
POST /books/{id}/transactions |
پایینتر |
GET/DELETE /books/{id}/transactions/{tx} |
یک تراکنش |
GET /books/{id}/currencies · PUT /{code} |
ارزهایی که این دفتر میتواند نگه دارد |
GET /books/{id}/wallet |
موجودی به تفکیک ارز |
GET/POST /books/{id}/conversions |
جابهجایی ارزش بین دو ارز دفتر |
curl -X POST /api/v1/books/$BOOK/transactions \
-H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
-d '{"flow":"income","category":"فروش","amount":"250000","occurred_on":"2026-08-25"}'
تراکنش has_receipt و receipt_kind و receipt_file_name را همراه دارد.
خودِ شناسهٔ فایل برنمیگردد: مبهم است و محدود به همان پیامرسانی که فایل را
نگه داشته، پس اینجا معنایی ندارد و دادنش فقط دامنهٔ چیزی را که یک درز افشا
میکند بزرگتر میکند. پیوستکردن رسید جریان ربات است — هنوز نقطهٔ پایانی برای
آپلود وجود ندارد.
scope اختیاری است و از نوع دفتر مقدار میگیرد. currency پیشفرض ارز خود
دفتر است، و ارز دیگر باید از ارزهای فعالشدهٔ دفتر باشد و conversion_rate
همراه داشته باشد — آن نرخ روی تراکنش منجمد میشود و هرگز دوباره اعمال
نمیشود. هزینه به ارزی که دفتر بهاندازهٔ کافی ندارد رد میشود؛ ارز پایه
مستثناست.
POST /books هم currency میگیرد، و ندادنش حالا یعنی همان پیشفرضی که ربات
استفاده میکند. قبلاً یعنی IRR بود، و به همین دلیل دفتری که در ربات ساخته
شده بود هر تراکنشی را که اینجا ثبت میشد رد میکرد.
افزودن عضو نیازمند آن است که آن شخص از قبل حساب داشته باشد؛ با ایمیل یا تلفن پیدا میشود.
گزارشها¶
GET /books/{id}/reports/summary |
?period=1403-05 ← درآمد، هزینه، خالص، تعداد |
GET /books/{id}/reports/by-category |
?flow=expense&period= ← جمعها و سهمها |
GET /books/{id}/reports/export.csv |
همان دوره به شکل فایل، با تاریخ جلالی؛ سلول متنی که با = + - @ شروع شود یک ' اول میگیرد |
GET /books/{id}/reports/day |
?date=2026-03-19 ← همان تفکیک لیست روزانه: کاری، شخصی، قسط، پسانداز |
بودجه، بدهی، وام¶
GET/PUT /books/{id}/budgets |
سقف برای هر دسته؛ PUT چون هر دسته یکی دارد |
DELETE /books/{id}/budgets/{budget} |
|
GET/POST /books/{id}/debts |
?include_settled=true برای دیدن بستهشدهها |
POST /books/{id}/debts/{debt}/settle |
میبندد؛ با {"record_transaction": true} پول را هم ثبت میکند (درآمد اگر طلب توست، هزینه اگر بدهی). پیشفرض خاموش است: اگر فروش یا خرید اصلی قبلاً ثبت شده، ثبت پرداخت هم آن را دوبار میشمارد. تسویهٔ دوباره ۴۲۲ است. |
GET/POST /books/{id}/loans |
مبلغ و تعداد قسط، نه جمع کل |
POST /books/{id}/loans/{loan}/pay |
یک قسط و هزینهاش را ثبت میکند |
وام با هزینهٔ یک قسط و تعدادش توصیف میشود، چون وام برای آدم همینطور توصیف میشود: «دوازدهتا چهار میلیونی».
حقوق و خزانه¶
فقط دفتر تیمی و سازمانی. هر مسیر همان سرویسی را صدا میزند که ربات صدا میزند، پس سهمی که اینجا تعیین شود همان است که تلگرام گزارش میدهد، و فیش حقوقیای که در تلگرام محاسبه شده همان است که اینجا برمیگردد.
GET/POST /books/{id}/periods |
فهرست، یا بازکردن یکی |
PATCH /books/{id}/periods/{p} |
تغییر نام یا بازه؛ فیشها دوباره حساب میشوند و پرداختها میمانند |
DELETE /books/{id}/periods/{p} |
فقط تا وقتی فیشی صادر نکرده |
GET /books/{id}/periods/{p}/distribution |
درآمد − هزینه − خزانه = قابلتوزیع (هرگز زیر صفر؛ shortfall زیان ماهِ زیانده است) |
POST /books/{id}/periods/{p}/status/{status} |
فقط گذارهای مستندشده |
POST /books/{id}/periods/{p}/close |
یکجا تا قفل؛ ۴۲۲ بدون هیچ جابهجایی اگر فیشی نیست، فیشها کهنهاند یا کسی طلب دارد |
GET/PUT /books/{id}/shares |
چه کسی چه میبرد؛ PUT قاعدهٔ قبلی را تاریخبندی میکند |
DELETE /books/{id}/shares/{user} |
پرداخت به این عضو را از امروز متوقف کن؛ دورههای گذشته قاعده را نگه میدارند |
GET/PUT /books/{id}/periods/{p}/performance |
ساعت، روز، امتیاز — برای مبناهای سنجشی |
GET/POST /books/{id}/periods/{p}/adjustments |
پاداش و کسر، علامتدار |
POST …/adjustments/{a}/approve |
ثبت و تأیید دو کار جدا هستند |
POST /books/{id}/periods/{p}/calculate |
فیشها، با تمام ورودیها منجمد روی خودشان؛ دفعهٔ بعد سر جایشان بهروز میشوند و پرداختها میمانند |
DELETE /books/{id}/periods/{p}/payslips |
باطلکردن محاسبه — بعد از اولین پرداخت رد میشود |
GET /books/{id}/periods/{p}/payslips |
فیش همه، یا فقط خودت، بسته به دسترسی |
POST /books/{id}/payslips/{slip}/payments |
مرحلهای؛ قسطیبودن قاعده است؛ با هر ارزی که دفتر دارد |
DELETE /books/{id}/payslips/{slip}/payments/{pay} |
پسگرفتن پرداخت تا وقتی دوره باز است |
GET/POST/DELETE /books/{id}/funds |
صندوقهای خزانه |
GET/POST/DELETE /books/{id}/funds/{f}/rules |
چه چیزی تغذیهشان میکند |
وقتی هیچ عضوی سهم ندارد، calculate ۴۲۲ برمیگرداند، نه فهرست خالی.
اجرایی که چیزی تولید نمیکند چون اصلاً از سؤالش جواب نگرفتهایم، نتیجهٔ خالی
نیست.
پرداخت بهطور پیشفرض به ارز خود فیش است. ارز دیگر باید در دفتر فعال باشد و
دفتر به اندازهٔ کافی از آن داشته باشد، و باید conversion_rate به ارز فیش را
همراه داشته باشد؛ فیش آن را با همان نرخ حساب میکند، و هر پرداخت در پاسخ نرخ
خودش را دارد تا فهرست با paid جور دربیاید. نرخی که با ارز خود فیش فرستاده
شود نادیده گرفته میشود، همانطور که در تراکنش. reference حداکثر ۸۰ حرف است.
curl -X POST /api/v1/books/$BOOK/payslips/$SLIP/payments \
-H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
-d '{"amount":"50","currency":"USDT","conversion_rate":"229867"}'
حذف صندوقی که قبلاً پول برداشته رد میشود — دورهٔ پرداختشده به جای خالی اشاره میکرد. بهجایش غیرفعالش کن. حذف قاعده همیشه بیخطر است، چون آنچه دورههای گذشته برداشتهاند عکسبرداری شده، نه بازمحاسبه.
وبهوک¶
POST /api/v1/webhooks/{provider}/{secret}
همیشه ۲۰۰ میدهد، حتی برای بهروزرسانیای که شکست میخورد — سرویسدهندهای که خطا بگیرد دوباره میفرستد، و تکرار ابدی یک بهروزرسانی مسموم بدتر از دورانداختن و ثبت دلیلش است.
فقط وقتی در دسترس است که این پروسه برای سرو وبهوک پیکربندی شده باشد. وگرنه همهٔ مسیرها ۴۰۴ میدهند. برای اینکه بدانی آن راز داخل مسیر چه کار میکند امنیت را ببین.
سلامت¶
GET /healthz |
بدون دستزدن به چیزی جواب میدهد؛ بیمحابا صدایش بزن |
GET /readyz |
یک کوئری اجرا میکند؛ وقتی دیتابیس دور از دسترس است ۵۰۳ |
پروسهای که به دیتابیسش نمیرسد بالاست ولی بهدردنخور، و همین استقرارها هستند که سالم به نظر میرسند و نیستند.