المطوّرون
واجهة Lawia البرمجية والـ Webhooks
اربط أنظمة المحاسبة وشؤون العاملين والتقارير بـ Lawia: واجهة برمجية موثّقة بمفاتيح يتحكم فيها مكتبك، وWebhooks موقَّعة، وموجزات بيانات لـ Power BI وExcel.
البداية
- ينشئ مالك المكتب أو مديره مفتاحاً من الإعدادات، الواجهة البرمجية والـ Webhooks: باسمه، والشخص الذي يعمل باسمه، والنطاقات التي يقرؤها أو يعدّلها، ويوم انتهاء صلاحيته. يظهر المفتاح مرة واحدة، ولا يحتفظ Lawia إلا ببصمة منه.
- يرسل نظامك المفتاح مع كل طلب، في ترويسة Authorization.
- الواجهة البرمجية كلها موصوفة بصيغة OpenAPI 3 على https://lawia.io/api/v1/openapi.json، التي تقرؤها أدوات مثل Postman.
curl https://lawia.io/api/v1/me \
-H "Authorization: Bearer lawia_api_…"
ما يسري على كل طلب
- يعمل المفتاح باسم أحد العاملين في المكتب. ولا يقرأ ولا يعدّل إلا ما يستطيعه هذا الشخص على الشاشة: القضايا التي يتيح له دوره ومشاركة كل قضية رؤيتها، ولا يصل أبداً إلى قضية حُجب عنها بحاجز أخلاقي، ولا إلى مهمة أو مستند خاص بغيره. ونطاقات المفتاح تضيّق ذلك أكثر.
- لكل مفتاح 120 طلباً في الدقيقة. وما يزيد على ذلك يُرَد عليه بـ 429 مع ترويسة Retry-After بعدد الثواني التي ينتظرها؛ وتبيّن X-RateLimit-Remaining عدد الطلبات المتبقية في هذه الدقيقة.
- يُسجَّل كل طلب في سجل التدقيق الخاص بالمكتب باسم المفتاح: ما قُرئ، وما أُضيف، وما رُفض.
- تأتي القوائم في صفحات: page وpage_size (100 ما لم يُحدَّد، و500 على الأكثر)، مع total، وnext وprevious عناوين كاملة. وتُرجع changed_since ما تغيّر في وقت ما أو بعده فقط، للمزامنة الليلية.
- المبالغ نصوص عشرية بعملة السجل نفسه، مثل 1500.00؛ والتواريخ بصيغة ISO 8601، والأوقات بالمنطقة الزمنية للمكتب.
- للخطأ حالة HTTP، ومحتوى فيه رمز، ورسالة بلغة ترويسة Accept-Language، والحقول التي بها الخطأ:
HTTP/1.1 400 Bad Request
{"error": {"code": "invalid", "message": "Some fields need correcting.",
"fields": {"title": ["This field is required."]}}}
الرموز: invalid_key وkey_expired وkey_inactive وtoken_expired (401)؛ scope وrole_not_allowed (403)؛ not_found (404)؛ method_not_allowed (405)؛ rate_limited (429)؛ invalid وinvalid_json (400).
حسابات الخدمة
حساب الخدمة مفتاح يعمل باسم المكتب لا باسم شخص، لنظام يخص المكتب لا أحدًا من العاملين فيه. ويستمر في العمل إذا غادر أحد العاملين. ولا ينشئه إلا مالك المكتب، من الإعدادات، الواجهة البرمجية والـ Webhooks، باختيار المكتب في خانة «يعمل باسم»، ولا يلغيه إلا مالك.
- له النطاقات المختارة له وليس له صلاحيات أي شخص: نطاقاته وحدها تحدد ما يملك قراءته أو تعديله.
- يرى قضايا المكتب وما فيها، إلا أي قضية عليها حاجز أخلاقي وأي قضية مشاركة مع أشخاص محددين فقط. والحاجز الذي يُقام لاحقًا يحجب القضية عنه فورًا، ولا يظهر له الموكل الذي توجد حواجز على كل قضاياه. ولا يرى أبدًا المهام أو المستندات الخاصة بأحد.
- يعرض العاملين في المكتب بالاسم والمسمى الوظيفي، دون بريدهم الإلكتروني أو أدوارهم. ولا تُتاح له بيانات العملاء المحتملين، وتحتاج بيانات الفواتير والمدفوعات إلى نطاق الفواتير أيضًا.
- لفتح قضية عليه أن يحدد المحامي المسؤول، ولتسجيل وقت عليه أن يحدد صاحبه: فليس له قضايا ولا وقت خاص به.
- كل ما يقرؤه أو يعدّله مسجل في سجل التدقيق باسمه، ولا تصل إلى الـ Webhooks الخاصة به إلا البيانات التي يملك قراءتها.
بيانات عميل OAuth
للنظام الذي لا يستطيع الاحتفاظ بمفتاح ثابت، أنشئ معرّف عميل ورمزًا سريًا بدل المفتاح، من المكان نفسه، باسم شخص أو كحساب خدمة. يستبدل بهما نظامك من عنوان الرموز رمز دخول (Bearer) صالحًا لمدة ساعة، ويرسله كما يرسل المفتاح، ويطلب رمزًا جديدًا عند انتهاء مدته. ولا يظهر الرمز السري إلا مرة واحدة، ولا تحتفظ لوايا إلا ببصمة منه ومن كل رمز دخول.
curl -X POST https://lawia.io/api/v1/oauth/token \
-u "lawia_client_…:lawia_secret_…" \
-d grant_type=client_credentials
{"access_token": "lawia_token_…", "token_type": "Bearer", "expires_in": 3600,
"scope": "cases:read tasks:read"}
curl https://lawia.io/api/v1/me \
-H "Authorization: Bearer lawia_token_…"
- يُرسل معرّف العميل والرمز السري في ترويسة Basic Authorization، أو في النموذج باسم client_id وclient_secret، لا بالطريقتين معًا.
- لرمز الدخول صلاحيات عميله، ويتوقف فورًا إذا أُلغي العميل أو انتهت صلاحيته أو غادر الشخص الذي يعمل باسمه. والرمز الذي انتهت مدته يُرد عليه بالحالة 401 والرمز token_expired.
- تتبع الأخطاء معيار OAuth 2.0، بالحقلين error وerror_description: invalid_client (401)، وinvalid_request وunsupported_grant_type (400)، وtoo_many_requests (429).
- كل رمز دخول يُمنح أو يُرفض مسجل في سجل التدقيق، وكذلك كل طلب يتم به.
نقاط الاتصال
تبدأ كل العناوين بـ https://lawia.io/api/v1. تتطلب القراءة نطاق القراءة للمجال، والإضافة نطاق الكتابة.
GET /api/v1/me
المفتاح: باسم من يعمل، ونطاقاته، وموعد انتهاء صلاحيته.
GET /api/v1/people
العاملون الحاليون في المكتب، لتحديد المحامي المسؤول.
المعاملات: page، page_size
GET /api/v1/contacts contacts:read
الموكلون وجهات الاتصال.
المعاملات: page، page_size، changed_since، q، kind
GET /api/v1/contacts/{id} contacts:read
جهة اتصال واحدة.
POST /api/v1/contacts contacts:write
إضافة موكل أو جهة اتصال.
kindstring person (شخص، وهو الافتراضي) أو organization (جهة).first_namestring الاسم الأول للشخص؛ ويلزم للشخص اسم أول أو اسم عائلة.last_namestring اسم العائلة للشخص.namestring اسم الجهة.emailstring بريده الإلكتروني.phonestring هاتفه.preferred_languagestring ar (الافتراضي) أو en.notesstring ملاحظات.
GET /api/v1/case-templates cases:read
قوالب القضايا في المكتب، لفتح القضايا منها.
المعاملات: page، page_size
GET /api/v1/cases cases:read
القضايا التي يستطيع الشخص الذي يعمل المفتاح باسمه رؤيتها.
المعاملات: page، page_size، changed_since، status، client، q
GET /api/v1/cases/{id} cases:read
قضية واحدة.
POST /api/v1/cases cases:write
فتح قضية من قالب أو من مجال ممارسة. تبقى معلّقة حتى يُبَتّ في فحص التعارض الخاص بها في Lawia.
clientinteger، إلزامي معرّف الموكل.titlestring، إلزامي عنوان القضية.templateinteger معرّف قالب القضية: بمجال ممارسته ومراحله ومهامه ومجلداته.practice_areainteger معرّف مجال الممارسة، عند عدم وجود قالب.responsibleinteger معرّف المحامي المسؤول؛ صاحب المفتاح إن لم يُحدَّد. وعلى حساب الخدمة أن يحدده.originatinginteger معرّف المحامي الذي جلب القضية.descriptionstring وصف.court_case_numberstring رقم الدعوى لدى المحكمة.
GET /api/v1/time-entries time:read
قيود الوقت: قيود الشخص الذي يعمل المفتاح باسمه، وقيود الجميع إن كان يملك رؤيتها.
المعاملات: page، page_size، changed_since، from، to، case، person، status
POST /api/v1/time-entries time:write
تسجيل وقت للشخص الذي يعمل المفتاح باسمه، بسعر ساعته.
personinteger صاحب الوقت، لحساب الخدمة، وعليه أن يحدده.caseinteger معرّف القضية: قضية مفتوحة يستطيع رؤيتها.worked_onstring اليوم، وهو اليوم الحالي ما لم يُحدَّد.minutesinteger الوقت بالدقائق.hoursnumber أو الوقت بالساعات، مثل 1.5.narrativestring، إلزامي ما تم إنجازه.billableboolean true ما لم يُحدَّد.activitystring النشاط، مثل الصياغة.
GET /api/v1/expenses time:read
المصروفات، بنفس قواعد الوقت.
المعاملات: page، page_size، changed_since، from، to، case
GET /api/v1/invoices billing:read
الفواتير الصادرة للقضايا التي يستطيع الشخص الذي يعمل المفتاح باسمه رؤيتها.
المعاملات: page، page_size، changed_since، from، to، case، client، status
GET /api/v1/invoices/{id} billing:read
فاتورة واحدة.
GET /api/v1/payments billing:read
المدفوعات المستلمة.
المعاملات: page، page_size، changed_since، from، to، client
POST /api/v1/payments billing:write
تسجيل دفعة على فاتورة صادرة، ويصل الموكلَ إيصال.
invoiceinteger، إلزامي معرّف الفاتورة.amountstring، إلزامي المبلغ بعملة الفاتورة، مثل 1500.00.methodstring transfer (تحويل، وهو الافتراضي) أو cash (نقداً) أو card (بطاقة) أو wallet (محفظة) أو check (شيك).received_onstring يوم استلامها، وهو اليوم الحالي ما لم يُحدَّد.referencestring مرجع البنك أو الإيصال.
GET /api/v1/tasks tasks:read
المهام التي يستطيع الشخص الذي يعمل المفتاح باسمه رؤيتها.
المعاملات: page، page_size، changed_since، case، assignee، done
GET /api/v1/feeds/{dataset} reports:read
مجموعة بيانات تقارير لـ Power BI وExcel: الوقت أو الفواتير أو المدفوعات أو القضايا أو العملاء المحتملون، بكل الأعمدة.
المعاملات: page، page_size، from، to، format
GET /api/v1/audit-events audit:read
سجل التدقيق الخاص بالمكتب، من الأقدم إلى الأحدث، لنظام SIEM: ما تعرضه صفحة سجل التدقيق لصاحب المفتاح. ويحصل حساب الخدمة على كل الإدخالات إلا ما يخص القضايا التي لا يراها.
المعاملات: page، page_size، after، since
المعاملات
pageرقم الصفحة، بدءاً من 1.page_sizeعدد السجلات في الصفحة: 100 ما لم يُحدَّد، و500 على الأكثر.changed_sinceالسجلات التي تغيّرت (أو سُجِّلت، في المدفوعات والمصروفات) في هذا الوقت أو بعده فقط، بتاريخ أو وقت بصيغة ISO 8601، مثل 2026-11-01T08:00:00Z.fromمن هذا التاريخ، مثل 2026-11-01.toحتى هذا التاريخ وشاملاً له.caseالخاصة بهذه القضية فقط.clientالخاصة بهذا الموكل فقط.personالخاصة بهذا الشخص فقط.assigneeالمسندة إلى هذا الشخص فقط.statusالتي لها هذه الحالة فقط.qالتي يحتوي اسمها أو رقمها أو عنوانها على هذا النص فقط.kindperson (شخص) أو organization (جهة).donetrue للمهام المنجزة فقط، وfalse للمفتوحة فقط.formatcsv لملف CSV بكل السجلات (حتى 100,000) بدلاً من صفحات JSON.afterالإدخالات التالية لهذا الإدخال فقط، بمعرّفه: المؤشر الذي تعطيه كل صفحة، ويحتفظ به الموصّل لطلبه التالي.sinceالإدخالات المسجلة في هذا الوقت أو بعده فقط، بتاريخ أو وقت بصيغة ISO 8601، مثل 2026-11-01T08:00:00Z.
إضافة السجلات
تمر الإضافات بنفس ضوابط شاشات Lawia. تُفتح القضية من قالب بمراحله ومهامه ومجلداته، وتبقى معلّقة حتى يُبَتّ في فحص التعارض الخاص بها في Lawia. ويُسعَّر الوقت بسعر ساعة الشخص، وترفضه القضية التي بلغت سقف أتعابها. وتُسجَّل الدفعة على فاتورة صادرة، ويصل الموكلَ إيصال.
curl -X POST https://lawia.io/api/v1/cases \
-H "Authorization: Bearer lawia_api_…" -H "Content-Type: application/json" \
-d '{"client": 812, "title": "Supply contract review", "template": 3}'
curl -X POST https://lawia.io/api/v1/time-entries \
-H "Authorization: Bearer lawia_api_…" -H "Content-Type: application/json" \
-d '{"case": 1204, "minutes": 90, "narrative": "Drafted the reply memorandum"}'
curl -X POST https://lawia.io/api/v1/payments \
-H "Authorization: Bearer lawia_api_…" -H "Content-Type: application/json" \
-d '{"invoice": 5531, "amount": "12500.00", "method": "transfer", "reference": "CIB 448812"}'
الـ Webhooks
يُبلغ الـ Webhook نظامك حين يحدث شيء. يضيفه المالك أو المدير من الإعدادات، الواجهة البرمجية والـ Webhooks: عنوان https، والأحداث التي يُبلَّغ بها، ومفتاح API تحدد نطاقاته والشخص الذي يعمل باسمه ما يجوز إبلاغه به. ولا تُرسَل أبداً رسالة عن سجل لا يستطيع المفتاح رؤيته.
case.openedفتح قضيةcase.closedإغلاق قضيةcontact.createdإضافة موكل أو جهة اتصالinvoice.issuedإصدار فاتورةinvoice.paidسداد فاتورة بالكاملpayment.receivedتسجيل دفعةtime.createdتسجيل وقتtask.doneإنجاز مهمةdocument.addedإضافة مستند إلى قضيةhearing.changedإضافة جلسة أو مهلة أو تغيير موعدها أو تسجيل نتيجتها
كل رسالة طلب POST بمحتوى JSON: معرّف الحدث ونوعه، ووقت حدوثه، والمكتب، والسجل كما تعطيه الواجهة البرمجية.
POST /your/address
Lawia-Event: invoice.issued
Lawia-Delivery: 5f0c2a8e-7d1b-4c55-9a43-2b8f7e61c0d9
Lawia-Signature: t=1793865600,v1=8b4f…
{"id": "5f0c2a8e-7d1b-4c55-9a43-2b8f7e61c0d9", "type": "invoice.issued",
"created_at": "2026-11-05T10:00:00+02:00", "firm": {"id": 4, "name": "…"},
"data": {"id": 5531, "number": "INV-00412", "total": "12500.00", "currency": "EGP", …}}
في hearing.changed يبيّن data ما تغيّر: added عند الإضافة، وmoved عند تغيير الموعد، وresult عند تسجيل قرار المحكمة في الجلسة أو الوفاء بالمهلة. ويحمل الجلسة، ومعها قرارها متى سُجّل، أو المهلة.
{"id": "9a1d…", "type": "hearing.changed", "created_at": "2026-11-05T13:20:00+02:00", "firm": {"id": 4, "name": "…"},
"data": {"change": "moved",
"hearing": {"id": 3307, "type": "hearing", "title": "…", "case": {"id": 1204, "number": "26-COM-0112", "title": "…"},
"starts_at": "2026-12-03T09:00:00+02:00", "court": "…", "decision": null, …}}}
تحقق من كل رسالة قبل أن تثق بها: احسب HMAC-SHA256 للطابع الزمني t متبوعاً بنقطة ثم المحتوى الخام، بسرّ الـ Webhook، وقارنه بـ v1. ارفض الطابع الزمني الذي مضى عليه أكثر من خمس دقائق، واستخدم معرّف الحدث لتجاهل الرسالة التي تصل مرتين.
import hashlib, hmac, time
def is_from_lawia(secret, signature_header, body):
parts = dict(item.split("=", 1) for item in signature_header.split(","))
signed = parts["t"].encode() + b"." + body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(parts["t"])) < 300
return fresh and hmac.compare_digest(expected, parts["v1"])
ردّ بأي حالة 2xx خلال 10 ثوانٍ. وإلا أعاد Lawia المحاولة بعد دقيقة، ثم 5 دقائق، ثم 30 دقيقة، ثم بعد ساعات، وعدد المحاولات 8 على مدى يومين تقريباً. وبعد إخفاقات متتالية عددها 10 يُوقَف الـ Webhook مؤقتاً ويُبلَّغ مالكو المكتب، وما يحدث في الأثناء ينتظر حتى استئنافه. ولكل Webhook سجل إرسال تبلغ مدة الاحتفاظ به (بالأيام) 30، وزر لإرسال رسالة تجريبية.
سجل التدقيق في نظام SIEM
يستطيع فريق الأمن لديك إدخال سجل التدقيق الخاص بالمكتب إلى Microsoft Sentinel أو Splunk أو أي نظام SIEM آخر: إذ يعطي /api/v1/audit-events إدخالاته من الأقدم إلى الأحدث، لمفتاح له مجال سجل التدقيق.
- المفتاح الذي يعمل باسم شخص يعطي ما تعرضه له صفحة سجل التدقيق، ولذلك يحتاج دوره إلى صلاحية «عرض سجل التدقيق»، كما في دور المالك أو المسؤول. ومثل تلك الصفحة، يستبعد الإدخالات الخاصة بأي قضية حُجب عنها صاحبه بحاجز أخلاقي، وإذا ذكر إدخال آخر قضية كهذه جاء رقمها null.
- أما حساب الخدمة فيعطي كل الإدخالات إلا ما يخص قضية عليها حاجز أخلاقي أو قضية مشاركة مع أشخاص محددين فقط، وإذا ذكر إدخال آخر قضية كهذه جاء رقمها null. ولكي تصل كل الإدخالات إلى نظام SIEM لديك، اجعل المفتاح يعمل باسم مالك أو مسؤول لا يحجبه أي حاجز.
- لكل إدخال معرّفه؛ ووقته بتوقيت UTC؛ ورمز الإجراء واسمه بالإنجليزية؛ ومن قام به، باسم الشخص وبريده الإلكتروني أو باسم حساب الخدمة؛ ومفتاح API الذي جاء عن طريقه؛ وعنوان IP؛ والسجل الذي يخصه ومعرّف القضية؛ وتفاصيله كما حُفظت.
- احتفظ بالمؤشر (cursor): تعطيه كل صفحة، وإذا مرّرته في after في الطلب التالي جاءك ما سُجّل بعده، كل إدخال مرة واحدة وبالترتيب. وتأخذ since وقتًا بدلًا منه، لأول تحميل ابتداءً من يوم معين.
- يظهر الإدخال بعد أن يُحفظ ما سجّله، في ثوانٍ عادةً، فلا يتخطى المؤشر أبدًا إدخالًا لم يظهر بعد.
- ويشمل الموجز كل طلب إلى الواجهة البرمجية، ومنها قراءات الموصّل الخاص بك نفسه: فكل قراءة إدخال باسم المفتاح. ولا يستطيع أحد تعديل إدخال أو حذفه.
curl "https://lawia.io/api/v1/audit-events?after=48100&page_size=500" \
-H "Authorization: Bearer lawia_api_…"
{"data": [{"id": 48101, "time": "2026-11-09T08:15:02.481210Z", "action": "document.opened",
"action_label": "Document opened",
"by": {"type": "person", "id": 7, "name": "Mona Adel", "email": "mona@example.com"},
"api_key": null, "ip_address": "41.33.10.2",
"record": {"type": "matter", "id": "1204", "name": "26-COM-0112 · Supply contract review"},
"case": 1204, "details": {"document": "Supply contract.pdf", "version": 2}}, …],
"page": 1, "page_size": 500, "total": 37, "next": null, "previous": null,
"cursor": 48137, "has_more": false}
في Microsoft Sentinel، يقرؤه موصّل بلا كود، هو RestApiPoller من Codeless Connector Framework، بإعدادات مثل هذه: المفتاح في ترويسة Authorization بعد Bearer، والإدخالات في data، ورمز دائم (PersistentToken) يؤخذ من cursor ويُرسل في after. وفي قاعدة جمع البيانات (data collection rule)، خذ TimeGenerated من time. وفي Splunk أو أي نظام SIEM آخر، يؤدي الغرض نفسه مُدخل (input) يحتفظ بالمؤشر بين الطلبات.
"auth": {"type": "APIKey", "ApiKey": "[[parameters('apiKey')]",
"ApiKeyName": "Authorization", "ApiKeyIdentifier": "Bearer"},
"request": {"apiEndpoint": "https://lawia.io/api/v1/audit-events", "httpMethod": "GET",
"queryWindowInMin": 5, "rateLimitQPS": 1, "paginatedCallsPerSecond": 1},
"paging": {"pagingType": "PersistentToken", "nextPageParaName": "after",
"nextPageTokenJsonPath": "$.cursor", "hasNextFlagJsonPath": "$.has_more",
"pageSize": 500, "pageSizeParameterName": "page_size"},
"response": {"eventsJsonPaths": ["$.data"], "format": "json"}
Power BI وExcel
مجموعات البيانات الخمس في تقارير Lawia المخصصة، وهي الوقت والفواتير والمدفوعات والقضايا والعملاء المحتملون، متاحة بكل أعمدتها على /api/v1/feeds/time وما يماثله، في صفحات JSON أو، مع format=csv، في ملف CSV واحد. ومبالغ الفواتير والمدفوعات بعملة المكتب، بالسعر الذي تحتفظ به كل فاتورة؛ وتحدد from وto الفترة. ويحتاج المفتاح إلى نطاق موجزات التقارير.
في Power BI Desktop أو Excel، اختر Get Data ثم From Web ثم Advanced. أدخل عنوان الموجز، وأضف ترويسة باسم Authorization قيمتها Bearer ثم مسافة ثم المفتاح. وحدّثه كل ليلة.
let
Source = Csv.Document(Web.Contents("https://lawia.io/api/v1/feeds/time",
[Query = [format = "csv", from = "2026-01-01"],
Headers = [Authorization = "Bearer lawia_api_…"]]), [Encoding = 65001]),
Table = Table.PromoteHeaders(Source)
in
Table
يظل الإصدار 1 يعمل كما هو: قد تُضاف حقول جديدة إلى سجلاته، وأي تغيير يعطّل التكامل يأتي في الإصدار 2.