API حسابداری برای برنامهنویسان: تمام دفاتر از طریق REST
API حسابداری لجریانو هر کاری را که در داشبورد برای یک کسبوکار انجام میدهید در اختیار کد شما میگذارد: حسابها، اسناد، سالهای مالی، طرفحسابها، نرخ ارز، گزارشهای مالی، وبهوکها و ردپای حسابرسی. ورودی و خروجی JSON است و مستندات OpenAPI را میتوانید همانجا در مرورگر امتحان کنید.

- JSON روی HTTPS با مستندات OpenAPI
- REST
- کلید API زیر نظر مالک
- Bearer
- سقف پیشفرض درخواست برای هر کلید
- ۱۲۰ در دقیقه
- نوع رویداد وبهوک
- ۱۲
آدرس پایه
https://api.ledgeriano.com/api/v1هر کسبوکار کلیدهای خودش را دارد؛ کلید تعیین میکند با دفاتر کدام کسبوکار کار میکنید.
از صفر تا ثبت اولین سند قطعی در سه قدم
- ۱
ساخت کلید
بهعنوان مالک کسبوکار، در تنظیمات کسبوکار یک کلید API بسازید و دسترسی آن را انتخاب کنید: فقط خواندن یا خواندن و نوشتن. کلید را همان لحظه کپی کنید؛ متن کامل آن فقط یک بار نمایش داده میشود.
- ۲
یک درخواست خواندنی
درخواست
GET /accountsرا با هدرAuthorization: Bearer lgr_live_…بفرستید. پاسخ، کدینگ حسابهای آن کسبوکار است و هدرها سقف باقیمانده درخواست و موجودی اعتبار را نشان میدهند. - ۳
ثبت سند
درخواست
POST /journal-entriesرا با ردیفهای تراز و هدرIdempotency-Keyبفرستید. از کد یا شناسه حساب استفاده کنید؛ با"status": "posted"سند همان لحظه قطعی میشود و بدون آن بهصورت موقت برای بازبینی میماند.
ثبت سند حسابداری با curl، جاوااسکریپت یا پایتون
یک درخواست به سه زبان: سند فروش قطعی با مشتری روی ردیف دریافتنی. اگر درخواست را با همان کلید یکتاسازی تکرار کنید، بهجای سند دوم، همان سند قبلی برگردانده میشود.
خواندن حسابها و گزارشها
# Trial balance for the current fiscal year
curl https://api.ledgeriano.com/api/v1/reports/trial-balance \
-H "Authorization: Bearer $LEDGERIANO_API_KEY" \
-H "Accept: application/json" \
-H "Accept-Language: fa" # Persian error messages and labelsconst res = await fetch("https://api.ledgeriano.com/api/v1/accounts?postable=1", {
headers: {
Authorization: `Bearer ${process.env.LEDGERIANO_API_KEY}`,
Accept: "application/json",
},
});
const { data: accounts } = await res.json();
console.log(res.headers.get("X-RateLimit-Remaining"), accounts.length);import os
import requests
resp = requests.get(
"https://api.ledgeriano.com/api/v1/reports/balance-sheet",
params={"compare": 1}, # add the prior year column
headers={
"Authorization": f"Bearer {os.environ['LEDGERIANO_API_KEY']}",
"Accept": "application/json",
},
timeout=30,
)
report = resp.json()curl -X POST https://api.ledgeriano.com/api/v1/journal-entries \
-H "Authorization: Bearer $LEDGERIANO_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10482" \
-d '{
"entry_date": "2026-09-25",
"voucher_type": "SAL",
"reference": "INV-10482",
"description": "Online order 10482",
"status": "posted",
"lines": [
{ "account_code": "1111", "debit": 1250.00, "party_code": "C001" },
{ "account_code": "4101", "credit": 1250.00 }
]
}'const res = await fetch("https://api.ledgeriano.com/api/v1/journal-entries", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LEDGERIANO_API_KEY}`,
Accept: "application/json",
"Content-Type": "application/json",
"Idempotency-Key": "order-10482", // same key on retry = same entry
},
body: JSON.stringify({
entry_date: "2026-09-25",
voucher_type: "SAL",
reference: "INV-10482",
description: "Online order 10482",
status: "posted",
lines: [
{ account_code: "1111", debit: 1250.0, party_code: "C001" },
{ account_code: "4101", credit: 1250.0 },
],
}),
});
if (!res.ok) {
const err = await res.json(); // { code, message, errors }
throw new Error(`${res.status} ${err.code}: ${err.message}`);
}
const { data: entry } = await res.json();
console.log(entry.number, res.headers.get("X-Credits-Balance"));import os
import requests
resp = requests.post(
"https://api.ledgeriano.com/api/v1/journal-entries",
headers={
"Authorization": f"Bearer {os.environ['LEDGERIANO_API_KEY']}",
"Accept": "application/json",
"Idempotency-Key": "order-10482",
},
json={
"entry_date": "2026-09-25",
"voucher_type": "SAL",
"reference": "INV-10482",
"description": "Online order 10482",
"status": "posted",
"lines": [
{"account_code": "1111", "debit": 1250.00, "party_code": "C001"},
{"account_code": "4101", "credit": 1250.00},
],
},
timeout=30,
)
if resp.status_code >= 400:
err = resp.json()
raise RuntimeError(f"{resp.status_code} {err['code']}: {err['message']}")
entry = resp.json()["data"]
print(entry["number"], resp.headers.get("X-Credits-Balance"))کلید API که فقط مالک میسازد، عوض میکند و باطل میکند
کلید را بهصورت توکن Bearer بفرستید: Authorization: Bearer lgr_live_…. هدر X-API-Key هم پذیرفته میشود. هر کلید متعلق به یک کسبوکار است؛ اتصال سه شرکت یعنی سه کلید، و لو رفتن یک کلید دفاتر شرکتهای دیگر را در معرض خطر قرار نمیدهد.
کلیدی با دسترسی خواندن فقط میتواند مسیرهای خواندنی را فراخوانی کند و هر تغییری در داده به دسترسی نوشتن نیاز دارد؛ در غیر این صورت پاسخ 403 است. کلید میتواند تاریخ انقضا داشته باشد، چرخانده شود (کلید قبلی از کار میافتد و کلید تازه با همان تنظیمات صادر میشود) یا هر زمان باطل شود. هر درخواست با کلیدی که آن را فرستاده ثبت میشود.
Authorization: Bearer lgr_live_xxxxxxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
Accept-Language: en # or fa
Idempotency-Key: order-10482 # on createreadwriterotaterevokeexpires_atAPI چه چیزهایی را پوشش میدهد
همه مسیرها نسبت به آدرس پایهاند. ردیفهای سند هم شناسه (account_id، party_id، cost_center_id، project_id) و هم کد (account_code، party_code و مانند آن) را میپذیرند.
| منبع | مسیرها | توضیح |
|---|---|---|
| سالهای مالی | GET POST /fiscal-years، GET PUT DELETE /fiscal-years/{id}، POST /{id}/close، POST /{id}/reopen | بستن سال، سند اختتامیه و افتتاحیه را صادر میکند |
| حسابها | /accounts (CRUD)، GET /accounts/{id}/balance | کدینگ حسابهای سال مالی جاری |
| طرفحسابها | /parties (CRUD) | مشتریان و تأمینکنندگان |
| مراکز هزینه | /cost-centers (CRUD) | واحدها و شعب |
| پروژهها | /projects (CRUD) | پروژهها و قراردادها |
| انواع سند | /voucher-types (CRUD) | کدهایی مثل SAL، PUR، PAY |
| اسناد حسابداری | /journal-entries (CRUD)، POST /bulk-post، POST /{id}/post، POST /{id}/reverse، POST /{id}/duplicate، /{id}/attachments | سند موقت ویرایش میشود؛ سند قطعی با برگشت اصلاح میشود |
| نرخ ارز | GET POST /exchange-rates، DELETE /exchange-rates/{id} | وقتی سند نرخ مخصوص خودش را ندارد استفاده میشود |
| گزارشها | /reports/trial-balance، balance-sheet، income-statement، cash-flow، equity-changes، general-ledger، journal، party-balances، dashboard | همان اعداد داشبورد |
| وبهوکها | /webhooks (CRUD)، GET /{id}/deliveries، POST /{id}/test، POST /{id}/deliveries/{delivery}/redeliver | امضاشده با HMAC SHA-256 |
| ردپای حسابرسی | GET /audit-logs | چه کسی، چه کاری، چه زمانی و از کجا |
کلید یکتاسازی، خطاها، سقف درخواست و اعتبار
کلید یکتاسازی (Idempotency)
هنگام ثبت سند هدرIdempotency-Key(یا فیلدidempotency_key) را بفرستید. تکرار درخواست با همان کلید، سند موجود را با کد وضعیت 200 بهجای 201 برمیگرداند؛ قطع شدن شبکه هرگز یک فروش را دو بار ثبت نمیکند.ساختار واحد خطا
هر خطا یکcodeماشینی، یکmessageفارسی یا انگلیسی (باAccept-Language: fa) و در خطای اعتبارسنجی،errorsبه تفکیک فیلد مثلlines.0.debitدارد.سقف درخواست
هر کلید بهطور پیشفرض ۱۲۰ درخواست در دقیقه مجاز است. هدرهایX-RateLimit-LimitوX-RateLimit-Remainingرا دنبال کنید؛ پاسخ429مقدارretry_afterرا به ثانیه دارد.اعتبار در هدرها
هر درخواست هزینهapi.callرا دارد و عملیاتها هزینه خودشان را اضافه میکنند، مثلentry.create. هدرهایX-Credits-ChargedوX-Credits-Balanceهزینه و مانده را نشان میدهند؛ پاسخ402یعنی اعتبار تمام شده است.
نمونه خطا
HTTP/1.1 422 Unprocessable Entity
{
"message": "The entry is not balanced: debits 1,250.00 vs credits 1,200.00 (difference 50.00).",
"code": "validation_failed",
"errors": {
"lines": ["The entry is not balanced: debits 1,250.00 vs credits 1,200.00 (difference 50.00)."],
"lines.1.account_id": ["Account 9999 was not found in this fiscal year."]
}
}| کد وضعیت | معنا |
|---|---|
| 401 | کلید API ارسال نشده، نامعتبر، منقضی یا باطلشده است |
| 402 | اعتبار کافی نیست (insufficient_credits) |
| 403 | کلید دسترسی write یا مجوز لازم را ندارد |
| 404 | این منبع در این کسبوکار وجود ندارد |
| 409 | با وضعیت فعلی در تضاد است، مثل ویرایش سند قطعی یا ثبت در سال بستهشده |
| 422 | اعتبارسنجی ناموفق بود؛ errors را به تفکیک فیلد ببینید |
| 423 | کسبوکار یا حساب مالک تعلیق شده است |
| 429 | سقف درخواست پر شده؛ پس از retry_after ثانیه دوباره تلاش کنید |
وبهوک امضاشده برای اسناد، حسابها، طرفحسابها و سالهای مالی
یک آدرس را برای رویدادهای موردنیازتان ثبت کنید تا لجریانو برای هر رویداد یک درخواست JSON از نوع POST بفرستد. هر ارسال هدرهای X-Ledgeriano-Event، شناسه یکتای X-Ledgeriano-Delivery، X-Ledgeriano-Timestamp و X-Ledgeriano-Signature را دارد.
امضا عبارت sha256= و پس از آن HMAC SHA-256 رشته timestamp + "." + raw_body با کلید مخفی وبهوک شماست. ارسالهای ناموفق تا ۵ بار با فاصله روبهافزایش (از ۳۰ ثانیه تا ۳۰ دقیقه) تکرار میشوند و میتوانید از طریق API آنها را ببینید، آزمایش کنید و دوباره بفرستید.
رویدادها
entry.createdentry.updatedentry.postedentry.reversedentry.deletedaccount.createdaccount.updatedaccount.deletedfiscal_year.createdfiscal_year.closedparty.createdparty.updated
یک نمونه ارسال
POST /webhooks/ledgeriano
X-Ledgeriano-Event: entry.posted
X-Ledgeriano-Delivery: 5f0c7b1e-2a41-4c1e-9d3a-8b7f7f1c2e90
X-Ledgeriano-Timestamp: 1790000000
X-Ledgeriano-Signature: sha256=9c1f...e04a
{
"id": "5f0c7b1e-2a41-4c1e-9d3a-8b7f7f1c2e90",
"event": "entry.posted",
"business_id": 12,
"created_at": "2026-09-25T10:42:07+00:00",
"data": { "entry": { "number": 318, "status": "posted", "lines": [ ... ] } }
}بررسی امضا
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.LEDGERIANO_WEBHOOK_SECRET;
// Keep the raw body: the signature covers the exact bytes we sent.
app.post("/webhooks/ledgeriano", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.get("X-Ledgeriano-Timestamp") ?? "";
const signature = req.get("X-Ledgeriano-Signature") ?? "";
const expected =
"sha256=" +
crypto.createHmac("sha256", SECRET).update(timestamp + "." + req.body.toString("utf8")).digest("hex");
const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300; // 5 minutes
if (!valid || !fresh) return res.sendStatus(401);
const event = JSON.parse(req.body.toString("utf8"));
// event.event === "entry.posted", event.data holds the entry
res.sendStatus(200);
});<?php
$secret = getenv('LEDGERIANO_WEBHOOK_SECRET');
$body = file_get_contents('php://input'); // raw body
$timestamp = $_SERVER['HTTP_X_LEDGERIANO_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_LEDGERIANO_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);
if (! hash_equals($expected, $signature) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
// $event['event'] === 'entry.posted', $event['data'] holds the entry
http_response_code(200);پرسشهای رایج درباره API حسابداری
آیا میتوانم API حسابداری را رایگان امتحان کنم؟
بله. حسابهای جدید اعتبار رایگان میگیرند. یک کسبوکار بسازید، کلید تولید کنید و API را فراخوانی کنید؛ هر درخواست مقدار کمی اعتبار مصرف میکند که در هدر X-Credits-Charged میبینید.
آیا یک کلید API به چند کسبوکار دسترسی دارد؟
خیر. هر کلید دقیقاً به یک کسبوکار تعلق دارد. این کار اتصالها را از هم جدا نگه میدارد و باطل کردن یک کلید روی بقیه اثری ندارد.
سند قطعی را چطور با API اصلاح کنم؟
مسیر POST /journal-entries/{id}/reverse را فراخوانی کنید. لجریانو سند برگشتی قطعی با جابهجایی بدهکار و بستانکار میسازد و سند اصلی را برگشتخورده علامت میزند. سپس سند درست را ثبت کنید.
آیا API از سند ارزی پشتیبانی میکند؟
بله. currency_code و exchange_rate را روی سند بفرستید، یا نرخ را نفرستید تا آخرین نرخ ذخیرهشده در /exchange-rates استفاده شود.
مستندات کامل مسیرها کجاست؟
مستندات مرجع OpenAPI با Scalar در api.ledgeriano.com/docs/api است. برای آموزش گامبهگام با مثال، راهنمای API حسابداری را بخوانید.
اتصال خودتان را از امروز بسازید
ثبتنام کنید، کسبوکار بسازید و کلید تولید کنید. اعتبار رایگان برای آزمایشهای اول کافی است؛ بعد از آن به ازای هر درخواست پرداخت میکنید، مطابق صفحه تعرفهها.