سرد الأدوار
كل دور على مساحة العمل، المدمجة أولًا، مع عدد من يحمل كلًّا منها من الأشخاص والمفاتيح.
ينفّذ الاستدعاء الحقيقي على مساحة عملك، بمفتاحك أنت.
GET /roles
كل دور على مساحة العمل، المدمجة أولًا، مع عدد من يحمل كلًّا منها من الأشخاص والمفاتيح.
محوران، وليسا السؤال نفسه
export OE=https://api.openemail.ukexport AUTH="Authorization: Bearer $OPENEMAIL_API_KEY"الدور يقول ما يُسمح لشخص بفعله في مساحة العمل هذه: قراءة البريد، وإرساله، وتحرير القوالب، وإضافة نطاق. والمنح يقول أي العناوين يُسمح له بفعله عليها، ويقيم في الجوار على /members/{userId}/addresses بقيمة member (يقرأ العنوان ويرسل باسمه) أو viewer (يقرأه فقط). وكلاهما يجب أن يتفق قبل أن تخرج رسالة: فالدور الذي يحمل emails:send بلا منح يستطيع الإرسال من لا شيء، وكل عنوان في مساحة العمل تحت منح viewer يستطيع الإرسال من لا شيء أيضًا.
كل مساحة عمل تُزرع بالأدوار الستة نفسها. Owner وAdmin وMember وViewer تشكّل سلّمًا. يحمل كل واحد منها كل ما يحمله الذي يليه، فخفض رتبة شخص يضيّق وصوله لا يستبدله بشريحة مختلفة. أما Developer وBilling فليسا درجتين عليه: Developer يبني التكاملات (المفاتيح والويب هوك والقوالب والإرسال) ولا يقرأ شيئًا من بريد مساحة العمل، وBilling يرى الخطة والفواتير ولا شيء غير ذلك. وكلاهما يقع بدقة داخل Admin. وتُزرع عند أول قراءة لا عند إنشاء مساحة العمل، فمساحة عمل صُنعت قبل وجود هذه الميزة تُنبتها لحظة أن يطلبها أي شيء. وbuiltin يسمّي أي زرع جاء منه الصف، وهذا كل ما يسمّيه: فالستة نقطة بداية يُفترض أن تشكّلها مساحة العمل، وكلها عدا Owner يمكن إعادة تسميتها وإعادة تحديد أذوناتها وحذفها. تفرّع على editable وdeletable لا على الاسم: فالدور الذي أعاد أحدهم تسميته ما زال يجيب عنهما بصحة، ولم يعد اسمه يخبرك بشيء.
المالك هو الاستثناء الوحيد، وهو استثناء في كل اتجاه: editable: false وdeletable: false، ومرفوض كهدف على PATCH /members/{userId}. فهو يصف الحساب الذي تُبنى عليه مساحة العمل ويحمل كل إذن، بما في ذلك أذونات تُضاف في إصدار لاحق، ولذلك تُحسَب قائمته بدل أن تُخزَّن. وجعل شخص آخر مالكًا هو نقل لمساحة العمل؛ ولا توجد هنا نقطة نهاية تؤديه.
أما الخمسة الأخرى فتقبل كل شيء: قائمة أذونات جديدة، ووصفًا جديدًا، واسمًا جديدًا، وDELETE. فهي افتراضات مزروعة لا تركيبات ثابتة: مساحة عمل لا تبني تكاملًا قط ينبغي أن تستطيع التخلص من Developer، وأخرى تعني بـ «Member» شيئًا أضيق ينبغي أن تستطيع قول ذلك بكلماتها. المالك وحده يرفض، ويرفض كل ذلك تحت رمز واحد: role_immutable، وهو 409 يحمل param: "roleId"، سواء حمل الـ PATCH اسمًا أو قائمة أذونات. لم تعد إعادة التسمية تُرفَض وحدها، فلا توجد حالة عدم قابلية للتغيير بـ param: "name" للتعامل معها؛ و409 الوحيد الذي ما زال بإمكان الاسم إثارته هو role_name_taken، حين يجيب عليه دور آخر على مساحة العمل أصلًا.
بعد الستة، تكتب مساحة العمل حتى 24 دورًا خاصًا بها. والسقف يحسب تلك وحدها، فحذف دور مزروع لا يشتري مساحة تحته. والأذونات تُوسَّع عند الدخول بدل أن تُؤخذ حرفيًا (فـ templates:write وحده يُخزَّن كـ templates:read وtemplates:write)، فاقرأ القائمة من الاستجابة بدل أن تفترض أنها التي أرسلتها.
الدور هو أيضًا السقف على مفتاح API. فالمفتاح الصادر مقابل دور يُسمح له بـ key.scopes ∩ role.permissions ولا أكثر، محسومًا لكل طلب عند الحدود، فتحرير دور يغيّر ما يُسمح لمفاتيحه بفعله في ندائها التالي مباشرة، والمفتاح بلا دور بلا سقف إطلاقًا. صفحة الصلاحيات تحتوي على ذلك كله.
مثال
يتطلب roles:read. بلا cursor. والغلاف يحمل hasMore وnextCursor حتى يستطيع العميل تمريره إلى شيفرة السرد نفسها التي يمرر إليها كل مجموعة أخرى، ولا توجد صفحة ثانية أبدًا.
curl "$OE/roles" -H "$AUTH"{ "object": "list", "data": [ { "object": "role", "id": "role_1c94e05d3862c1f0a44b7f3a", "name": "Owner", "description": "The person the workspace belongs to. Holds everything, including additions.", "permissions": ["emails:send", "emails:read", "…", "workspace:manage"], "builtin": "owner", "editable": false, "deletable": false, "members": 0, "apiKeys": 2, "createdAt": "2026-08-01T09:00:00.000Z", "updatedAt": "2026-08-01T09:00:00.000Z" }, { "object": "role", "id": "role_c40a95f21cc65d31c2a89e07", "name": "Viewer", "description": "Reads the mail on the addresses they hold, and changes nothing.", "permissions": [ "emails:read", "drafts:read", "threads:read", "labels:read", "contacts:read", "calendar:read", "templates:read", "rules:read", "connections:read", "settings:read" ], "builtin": "viewer", "editable": true, "deletable": true, "members": 3, "apiKeys": 1, "createdAt": "2026-08-01T09:00:00.000Z", "updatedAt": "2026-08-01T09:00:00.000Z" } ], "hasMore": false, "nextCursor": null}مرتَّبة حسب رتبة الدور المدمج ثم الاسم (owner, admin, member, viewer, developer, billing، ثم البقية أبجديًا) لا حسب الأحدث أولًا كبقية الـ API. فمصفوفة الأذونات تُقرأ كسلّم، وترتيبها بـ createdAt يضع أوسع دور في صف مختلف كل أسبوع.
قراءة هذه القائمة هي ما يزرع الستة على مساحة عمل لم تحصل عليها قط. والزرع يتعارض على فهرس فريد ولا يفعل شيئًا في المرة الثانية، فالنداء عديم الأثر الجانبي عند التكرار والأول وحده هو من يكتب، وهو أيضًا سبب قدرة POST /members دائمًا على تسمية roleId موجود.
يزرع مرة واحدة. تسجّل مساحة العمل أنها زُرعت، فهذه القراءة تملأ مساحة عمل أقدم من الميزة ثم لا تكتب أبدًا بعد ذلك، وهو ما يجعل حذف دور مزروع نهائيًا. وقد كانت نسخة أسبق تعيد إدراج أي صف قالب ناقص عند كل قراءة، فكان Billing المحذوف يعود تحت id جديد عند تحميل الصفحة التالية؛ لم يعد ذلك يحدث.
members وapiKeys هما ما يجب نقله قبل أن يذهب الدور، وهو ما يتيح للعميل التحذير قبل عرض الحذف لا بعد الـ 409. وصف المالك يقرأ عادةً members: 0: فالمالك ليس عضوًا في مساحة عمله، بل هو الحساب الذي تُبنى عليه.
هناك سقف صارم بـ 24 دورًا مخصصًا تحديدًا حتى تكون هذه استجابة واحدة. فمساحة عمل بأربعين دورًا لا تستطيع أن تجيب بالنظر عن «من يستطيع الإرسال باسم billing@»، وهو السؤال الوحيد الذي وُجدت الميزة لجعله قابلًا للإجابة.