---
title: "Discussions | Fareeqy API"
description: "Majlis discussion topics within a project. Ref ids (mjl-); a slug still resolves. The Discussions endpoints in the Fareeqy REST API: parameters"
url: https://fareeqy.com/api/v1/docs/discussions/en
language: en
---

# Discussions

Majlis discussion topics within a project. Ref ids (mjl-); a slug still resolves.

Base URL `https://app.fareeqy.com/api/v1`

Contents

On this page

-   The resource
-   List a project's majlis topics
-   Create (and publish) a majlis topic
-   Get a majlis topic
-   Update a majlis topic
-   Delete a majlis topic
-   Reply to a majlis topic
-   Get a majlis topic by ref
-   Update a majlis topic by ref
-   Delete a majlis topic by ref
-   Reply to a majlis topic by ref

Endpoints

-   [Account](https://fareeqy.com/api/v1/docs/account/en)
-   [Events](https://fareeqy.com/api/v1/docs/events/en)
-   [Projects](https://fareeqy.com/api/v1/docs/projects/en)
-   [Members](https://fareeqy.com/api/v1/docs/members/en)
-   [Task lists](https://fareeqy.com/api/v1/docs/task-lists/en)
-   [Tasks](https://fareeqy.com/api/v1/docs/tasks/en)
-   [Comments](https://fareeqy.com/api/v1/docs/comments/en)
-   [Discussions](https://fareeqy.com/api/v1/docs/discussions/en)
-   [Files](https://fareeqy.com/api/v1/docs/files/en)
-   [Folders](https://fareeqy.com/api/v1/docs/folders/en)

More

-   [Changelog](https://fareeqy.com/api/v1/docs/changelog/en)

On this page

-   The resource
-   List a project's majlis topics
-   Create (and publish) a majlis topic
-   Get a majlis topic
-   Update a majlis topic
-   Delete a majlis topic
-   Reply to a majlis topic
-   Get a majlis topic by ref
-   Update a majlis topic by ref
-   Delete a majlis topic by ref
-   Reply to a majlis topic by ref

Endpoints

-   [Account](https://fareeqy.com/api/v1/docs/account/en)
-   [Events](https://fareeqy.com/api/v1/docs/events/en)
-   [Projects](https://fareeqy.com/api/v1/docs/projects/en)
-   [Members](https://fareeqy.com/api/v1/docs/members/en)
-   [Task lists](https://fareeqy.com/api/v1/docs/task-lists/en)
-   [Tasks](https://fareeqy.com/api/v1/docs/tasks/en)
-   [Comments](https://fareeqy.com/api/v1/docs/comments/en)
-   [Discussions](https://fareeqy.com/api/v1/docs/discussions/en)
-   [Files](https://fareeqy.com/api/v1/docs/files/en)
-   [Folders](https://fareeqy.com/api/v1/docs/folders/en)

More

-   [Changelog](https://fareeqy.com/api/v1/docs/changelog/en)

## The resource

The fields returned inside `data` by the endpoints in this group.

`id` string

The majlis topic ref. A slug is still accepted anywhere this is passed back.

`url` string | null

Absolute address of the page a person opens in فريقي.

`title` string

`body` string | null

Plain-text rendering of the rich-text body.

`category` string | null

`author` string | null

`replies_count` integer

`created_at` string (date-time)

Discussion Copy

 `{   "id": "mjl-6khz2",   "url": "https://app.fareeqy.com/majlis/mjl-6khz2",   "title": "اقتراح لتحسين صفحة التسعير",   "body": "أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة.",   "category": "فكرة",   "author": "سارة العتيبي",   "replies_count": 4,   "created_at": "2026-07-08T14:02:55.000+03:00" }`

### List a project's majlis topics

GET /projects/ {project\_id} /discussions Requires `discussions:read`

Published topics only: drafts and deleted topics are not listed. Newest first.

#### Path parameters

`project_id` string required

The project REF, or its slug.

#### Query parameters

`limit` integer

Page size (default 50, max 100, clamped).

Default: `50`

`offset` integer

Number of records to skip (default 0).

Default: `0`

#### Request

cURL Copy

 `curl "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/discussions" \   -H "Authorization: Bearer $FAREEQY_API_KEY"`

#### Response

200 Copy

 `{   "data": [     {       "id": "mjl-2wq7d",       "url": "https://app.fareeqy.com/majlis/mjl-2wq7d",       "title": "قرار: توحيد هوية التقارير المُصدَّرة",       "body": "اتفقنا في اجتماع الاثنين على أن كل تقرير يخرج من فريقي يحمل هوية الشركة نفسها: الشعار في الترويسة، ولون العناوين #00674F، والتاريخ بالهجري والميلادي معاً في الصفحة الأولى.\n\nالسبب أن التقارير التي أرسلناها للمستثمرين الربع الماضي خرجت بثلاثة أشكال مختلفة لأن كل واحد منا صدّرها من شاشة مختلفة، فبدت وكأنها من ثلاث شركات.\n\nالتنفيذ على فريق التصميم قبل نهاية الشهر، والمرجع ملف الهوية المرفق في مجلد «الهوية البصرية». من عنده اعتراض فليكتبه هنا قبل الأحد، وبعدها يصير القرار نافذاً.",       "category": "قرار",       "author": "سارة العتيبي",       "replies_count": 7,       "created_at": "2026-07-21T09:12:44.000+03:00"     },     {       "id": "mjl-6khz2",       "url": "https://app.fareeqy.com/majlis/mjl-6khz2",       "title": "اقتراح لتحسين صفحة التسعير",       "body": "أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة.\n\nلاحظت من تسجيلات الجلسات أن الزائر يفتح البطاقات الأربع واحدة بعد الأخرى ثم يعود للأعلى، وهذا يعني أنه يحاول المقارنة ولا تسعفه الواجهة. الجدول يحل هذا في نظرة واحدة.",       "category": "فكرة",       "author": "عبدالله المطيري",       "replies_count": 4,       "created_at": "2026-07-08T14:02:55.000+03:00"     },     {       "id": "mjl-9nc4t",       "url": "https://app.fareeqy.com/majlis/mjl-9nc4t",       "title": "إجازة فريق التطوير الأسبوع القادم",       "body": "فريق التطوير في إجازة من الأحد إلى الثلاثاء. أي طلب عاجل يُرفع هنا وسنتابعه، وما عدا ذلك يُجدول بعد العودة.",       "category": "إعلان",       "author": "نورة الحربي",       "replies_count": 2,       "created_at": "2026-07-02T08:30:10.000+03:00"     }   ],   "meta": {     "total": 3,     "limit": 50,     "offset": 0,     "count": 3   } }`

Refusals: 401 403 404 429 Show failure examples Hide failure examples

401 `default`

Missing or invalid API key.

default Copy

 `{   "error": {     "code": "unauthorized",     "message": "مفتاح الـ API غير صالح أو غير مُرسَل."   } }`

403 `forbidden`

Two different refusals share this status, and a client must tell them apart by `error.code`. `forbidden` means the key's scope or allowlist does not permit this operation, or Pundit denied the action. `plan_upgrade_required` means the company's plan carries no API access at all, so no key on it can ever succeed and there is nothing to retry.

This key may not perform this operation Copy

 `{   "error": {     "code": "forbidden",     "message": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."   } }`

The company's plan carries no API access Copy

 `{   "error": {     "code": "plan_upgrade_required",     "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."   } }`

404 `not_found`

Resource not found or not accessible — also returned for EVERY endpoint when the company's `rest_api` feature flag is disabled (the surface is hidden). Lookups drill through the URL hierarchy, so another company's record is a 404 and never a leak. A path that matches no route at all answers 404 with the distinct code `unknown_endpoint` and echoes the path back, so a mistyped or half-built URL is told apart from a record that is missing or out of reach.

A record that is missing or out of reach Copy

 `{   "error": {     "code": "not_found",     "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."   } }`

A path that matches no endpoint Copy

 `{   "error": {     "code": "unknown_endpoint",     "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."   } }`

429 `rate_limit_exceeded`

Either the company's daily API allowance is spent ( `rate_limit_exceeded` ), or the per-key / per-IP burst throttle of 300 requests per minute fired. Both come back after a wait, so `Retry-After` is honest here. **The two bodies are not the same shape.** The daily-quota refusal uses the standard error envelope. The burst throttle is served by Rack::Attack ahead of the application, so its body is a flat `{"error": "<string>"}` with no `code`. A client that reads `error.code` has to tolerate `error` being a plain string.

Today's daily allowance is spent (application envelope) Copy

 `{   "error": {     "code": "rate_limit_exceeded",     "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh."   } }`

Over 300 requests in a minute (Rack::Attack body, flat error) Copy

 `{   "error": "Rate limit exceeded. Please try again later." }`

[Errors](https://fareeqy.com/api/v1/docs/en#errors)

### Create (and publish) a majlis topic

POST /projects/ {project\_id} /discussions Requires `discussions:write`

Creates and PUBLISHES the topic — subscribing its audience, notifying them, and recording the timeline event.

#### Path parameters

`project_id` string required

The project REF, or its slug.

#### Request body

`title` string required

`body` string | null

Rich-text body (plain text in).

#### Request

cURL Copy

 `curl -X POST "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/discussions" \   -H "Authorization: Bearer $FAREEQY_API_KEY" \   -H "Content-Type: application/json" \   -d '{"title":"اقتراح لتحسين صفحة التسعير","body":"أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة."}'`

#### Response

201 Copy

 `{   "data": {     "id": "mjl-6khz2",     "url": "https://app.fareeqy.com/majlis/mjl-6khz2",     "title": "اقتراح لتحسين صفحة التسعير",     "body": "أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة.",     "category": "فكرة",     "author": "سارة العتيبي",     "replies_count": 4,     "created_at": "2026-07-08T14:02:55.000+03:00"   } }`

Refusals: 401 403 404 409 422 429 Show failure examples Hide failure examples

401 `default`

Missing or invalid API key.

default Copy

 `{   "error": {     "code": "unauthorized",     "message": "مفتاح الـ API غير صالح أو غير مُرسَل."   } }`

403 `forbidden`

Two different refusals share this status, and a client must tell them apart by `error.code`. `forbidden` means the key's scope or allowlist does not permit this operation, or Pundit denied the action. `plan_upgrade_required` means the company's plan carries no API access at all, so no key on it can ever succeed and there is nothing to retry.

This key may not perform this operation Copy

 `{   "error": {     "code": "forbidden",     "message": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."   } }`

The company's plan carries no API access Copy

 `{   "error": {     "code": "plan_upgrade_required",     "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."   } }`

404 `not_found`

Resource not found or not accessible — also returned for EVERY endpoint when the company's `rest_api` feature flag is disabled (the surface is hidden). Lookups drill through the URL hierarchy, so another company's record is a 404 and never a leak. A path that matches no route at all answers 404 with the distinct code `unknown_endpoint` and echoes the path back, so a mistyped or half-built URL is told apart from a record that is missing or out of reach.

A record that is missing or out of reach Copy

 `{   "error": {     "code": "not_found",     "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."   } }`

A path that matches no endpoint Copy

 `{   "error": {     "code": "unknown_endpoint",     "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."   } }`

409 `default`

A uniqueness/record conflict; retry.

default Copy

 `{   "error": {     "code": "conflict",     "message": "تعذّر إتمام العملية بسبب تعارض. أعد المحاولة."   } }`

422 `default`

A caller-fixable bad request (validation error, bad date, bad enum).

default Copy

 `{   "error": {     "code": "unprocessable_entity",     "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."   } }`

429 `rate_limit_exceeded`

Either the company's daily API allowance is spent ( `rate_limit_exceeded` ), or the per-key / per-IP burst throttle of 300 requests per minute fired. Both come back after a wait, so `Retry-After` is honest here. **The two bodies are not the same shape.** The daily-quota refusal uses the standard error envelope. The burst throttle is served by Rack::Attack ahead of the application, so its body is a flat `{"error": "<string>"}` with no `code`. A client that reads `error.code` has to tolerate `error` being a plain string.

Today's daily allowance is spent (application envelope) Copy

 `{   "error": {     "code": "rate_limit_exceeded",     "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh."   } }`

Over 300 requests in a minute (Rack::Attack body, flat error) Copy

 `{   "error": "Rate limit exceeded. Please try again later." }`

[Errors](https://fareeqy.com/api/v1/docs/en#errors)

### Get a majlis topic

GET /projects/ {project\_id} /discussions/ {id} Requires `discussions:read`

#### Path parameters

`project_id` string required

The project REF, or its slug.

`id` string required

The majlis topic REF (mjl-8f3kd), or its slug (unique within its project).

#### Request

cURL Copy

 `curl "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/discussions/mjl-6khz2" \   -H "Authorization: Bearer $FAREEQY_API_KEY"`

#### Response

200 Copy

 `{   "data": {     "id": "mjl-6khz2",     "url": "https://app.fareeqy.com/majlis/mjl-6khz2",     "title": "اقتراح لتحسين صفحة التسعير",     "body": "أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة.",     "category": "فكرة",     "author": "سارة العتيبي",     "replies_count": 4,     "created_at": "2026-07-08T14:02:55.000+03:00"   } }`

Refusals: 401 403 404 429 Show failure examples Hide failure examples

401 `default`

Missing or invalid API key.

default Copy

 `{   "error": {     "code": "unauthorized",     "message": "مفتاح الـ API غير صالح أو غير مُرسَل."   } }`

403 `forbidden`

Two different refusals share this status, and a client must tell them apart by `error.code`. `forbidden` means the key's scope or allowlist does not permit this operation, or Pundit denied the action. `plan_upgrade_required` means the company's plan carries no API access at all, so no key on it can ever succeed and there is nothing to retry.

This key may not perform this operation Copy

 `{   "error": {     "code": "forbidden",     "message": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."   } }`

The company's plan carries no API access Copy

 `{   "error": {     "code": "plan_upgrade_required",     "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."   } }`

404 `not_found`

Resource not found or not accessible — also returned for EVERY endpoint when the company's `rest_api` feature flag is disabled (the surface is hidden). Lookups drill through the URL hierarchy, so another company's record is a 404 and never a leak. A path that matches no route at all answers 404 with the distinct code `unknown_endpoint` and echoes the path back, so a mistyped or half-built URL is told apart from a record that is missing or out of reach.

A record that is missing or out of reach Copy

 `{   "error": {     "code": "not_found",     "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."   } }`

A path that matches no endpoint Copy

 `{   "error": {     "code": "unknown_endpoint",     "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."   } }`

429 `rate_limit_exceeded`

Either the company's daily API allowance is spent ( `rate_limit_exceeded` ), or the per-key / per-IP burst throttle of 300 requests per minute fired. Both come back after a wait, so `Retry-After` is honest here. **The two bodies are not the same shape.** The daily-quota refusal uses the standard error envelope. The burst throttle is served by Rack::Attack ahead of the application, so its body is a flat `{"error": "<string>"}` with no `code`. A client that reads `error.code` has to tolerate `error` being a plain string.

Today's daily allowance is spent (application envelope) Copy

 `{   "error": {     "code": "rate_limit_exceeded",     "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh."   } }`

Over 300 requests in a minute (Rack::Attack body, flat error) Copy

 `{   "error": "Rate limit exceeded. Please try again later." }`

[Errors](https://fareeqy.com/api/v1/docs/en#errors)

### Update a majlis topic

PATCH /projects/ {project\_id} /discussions/ {id} Requires `discussions:write`

#### Path parameters

`project_id` string required

The project REF, or its slug.

`id` string required

The majlis topic REF (mjl-8f3kd), or its slug (unique within its project).

#### Request body

`title` string

`body` string | null

#### Request

cURL Copy

 `curl -X PATCH "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/discussions/mjl-6khz2" \   -H "Authorization: Bearer $FAREEQY_API_KEY" \   -H "Content-Type: application/json" \   -d '{"body":"أقترح نعرض الباقات في جدول مقارنة واحد، ونثبت زر التجربة أعلى الصفحة."}'`

#### Response

200 Copy

 `{   "data": {     "id": "mjl-6khz2",     "url": "https://app.fareeqy.com/majlis/mjl-6khz2",     "title": "اقتراح لتحسين صفحة التسعير",     "body": "أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة.",     "category": "فكرة",     "author": "سارة العتيبي",     "replies_count": 4,     "created_at": "2026-07-08T14:02:55.000+03:00"   } }`

Refusals: 401 403 404 409 422 429 Show failure examples Hide failure examples

401 `default`

Missing or invalid API key.

default Copy

 `{   "error": {     "code": "unauthorized",     "message": "مفتاح الـ API غير صالح أو غير مُرسَل."   } }`

403 `forbidden`

Two different refusals share this status, and a client must tell them apart by `error.code`. `forbidden` means the key's scope or allowlist does not permit this operation, or Pundit denied the action. `plan_upgrade_required` means the company's plan carries no API access at all, so no key on it can ever succeed and there is nothing to retry.

This key may not perform this operation Copy

 `{   "error": {     "code": "forbidden",     "message": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."   } }`

The company's plan carries no API access Copy

 `{   "error": {     "code": "plan_upgrade_required",     "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."   } }`

404 `not_found`

Resource not found or not accessible — also returned for EVERY endpoint when the company's `rest_api` feature flag is disabled (the surface is hidden). Lookups drill through the URL hierarchy, so another company's record is a 404 and never a leak. A path that matches no route at all answers 404 with the distinct code `unknown_endpoint` and echoes the path back, so a mistyped or half-built URL is told apart from a record that is missing or out of reach.

A record that is missing or out of reach Copy

 `{   "error": {     "code": "not_found",     "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."   } }`

A path that matches no endpoint Copy

 `{   "error": {     "code": "unknown_endpoint",     "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."   } }`

409 `default`

A uniqueness/record conflict; retry.

default Copy

 `{   "error": {     "code": "conflict",     "message": "تعذّر إتمام العملية بسبب تعارض. أعد المحاولة."   } }`

422 `default`

A caller-fixable bad request (validation error, bad date, bad enum).

default Copy

 `{   "error": {     "code": "unprocessable_entity",     "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."   } }`

429 `rate_limit_exceeded`

Either the company's daily API allowance is spent ( `rate_limit_exceeded` ), or the per-key / per-IP burst throttle of 300 requests per minute fired. Both come back after a wait, so `Retry-After` is honest here. **The two bodies are not the same shape.** The daily-quota refusal uses the standard error envelope. The burst throttle is served by Rack::Attack ahead of the application, so its body is a flat `{"error": "<string>"}` with no `code`. A client that reads `error.code` has to tolerate `error` being a plain string.

Today's daily allowance is spent (application envelope) Copy

 `{   "error": {     "code": "rate_limit_exceeded",     "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh."   } }`

Over 300 requests in a minute (Rack::Attack body, flat error) Copy

 `{   "error": "Rate limit exceeded. Please try again later." }`

[Errors](https://fareeqy.com/api/v1/docs/en#errors)

### Delete a majlis topic

DELETE /projects/ {project\_id} /discussions/ {id} Requires `discussions:destructive`

SOFT delete (recoverable by an admin).

#### Path parameters

`project_id` string required

The project REF, or its slug.

`id` string required

The majlis topic REF (mjl-8f3kd), or its slug (unique within its project).

#### Request

cURL Copy

 `curl -X DELETE "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/discussions/mjl-6khz2" \   -H "Authorization: Bearer $FAREEQY_API_KEY"`

#### Response

200 Copy

 `{   "data": {     "deleted": true,     "recoverable": true,     "discussion": {       "title": "اقتراح لتحسين صفحة التسعير",       "id": "mjl-6khz2"     }   } }`

Refusals: 401 403 404 429 Show failure examples Hide failure examples

401 `default`

Missing or invalid API key.

default Copy

 `{   "error": {     "code": "unauthorized",     "message": "مفتاح الـ API غير صالح أو غير مُرسَل."   } }`

403 `forbidden`

Two different refusals share this status, and a client must tell them apart by `error.code`. `forbidden` means the key's scope or allowlist does not permit this operation, or Pundit denied the action. `plan_upgrade_required` means the company's plan carries no API access at all, so no key on it can ever succeed and there is nothing to retry.

This key may not perform this operation Copy

 `{   "error": {     "code": "forbidden",     "message": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."   } }`

The company's plan carries no API access Copy

 `{   "error": {     "code": "plan_upgrade_required",     "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."   } }`

404 `not_found`

Resource not found or not accessible — also returned for EVERY endpoint when the company's `rest_api` feature flag is disabled (the surface is hidden). Lookups drill through the URL hierarchy, so another company's record is a 404 and never a leak. A path that matches no route at all answers 404 with the distinct code `unknown_endpoint` and echoes the path back, so a mistyped or half-built URL is told apart from a record that is missing or out of reach.

A record that is missing or out of reach Copy

 `{   "error": {     "code": "not_found",     "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."   } }`

A path that matches no endpoint Copy

 `{   "error": {     "code": "unknown_endpoint",     "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."   } }`

429 `rate_limit_exceeded`

Either the company's daily API allowance is spent ( `rate_limit_exceeded` ), or the per-key / per-IP burst throttle of 300 requests per minute fired. Both come back after a wait, so `Retry-After` is honest here. **The two bodies are not the same shape.** The daily-quota refusal uses the standard error envelope. The burst throttle is served by Rack::Attack ahead of the application, so its body is a flat `{"error": "<string>"}` with no `code`. A client that reads `error.code` has to tolerate `error` being a plain string.

Today's daily allowance is spent (application envelope) Copy

 `{   "error": {     "code": "rate_limit_exceeded",     "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh."   } }`

Over 300 requests in a minute (Rack::Attack body, flat error) Copy

 `{   "error": "Rate limit exceeded. Please try again later." }`

[Errors](https://fareeqy.com/api/v1/docs/en#errors)

### Reply to a majlis topic

POST /projects/ {project\_id} /discussions/ {id} /reply Requires `discussions:write`

Adds a reply. The reply is serialized with the same shape as a Comment.

#### Path parameters

`project_id` string required

The project REF, or its slug.

`id` string required

The majlis topic REF (mjl-8f3kd), or its slug (unique within its project).

#### Request body

`content` string required

Rich-text reply body (plain text in).

#### Request

cURL Copy

 `curl -X POST "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/discussions/mjl-6khz2/reply" \   -H "Authorization: Bearer $FAREEQY_API_KEY" \   -H "Content-Type: application/json" \   -d '{"content":"أتفق. الجدول أوضح، بشرط نبقي الأسعار ظاهرة بدون ضغط إضافي."}'`

#### Response

201 Copy

 `{   "data": {     "id": 512,     "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",     "content": "راجعت النسخة الأخيرة، ينقصنا حالة الخطأ في النموذج.",     "author": "عبدالله المطيري",     "created_at": "2026-07-15T09:48:03.000+03:00",     "updated_at": "2026-07-15T09:48:03.000+03:00",     "attachments": [       {         "id": 9031,         "name": "error-state.png",         "content_type": "image/png",         "byte_size": 184320       }     ]   } }`

Refusals: 401 403 404 409 422 429 Show failure examples Hide failure examples

401 `default`

Missing or invalid API key.

default Copy

 `{   "error": {     "code": "unauthorized",     "message": "مفتاح الـ API غير صالح أو غير مُرسَل."   } }`

403 `forbidden`

Two different refusals share this status, and a client must tell them apart by `error.code`. `forbidden` means the key's scope or allowlist does not permit this operation, or Pundit denied the action. `plan_upgrade_required` means the company's plan carries no API access at all, so no key on it can ever succeed and there is nothing to retry.

This key may not perform this operation Copy

 `{   "error": {     "code": "forbidden",     "message": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."   } }`

The company's plan carries no API access Copy

 `{   "error": {     "code": "plan_upgrade_required",     "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."   } }`

404 `not_found`

Resource not found or not accessible — also returned for EVERY endpoint when the company's `rest_api` feature flag is disabled (the surface is hidden). Lookups drill through the URL hierarchy, so another company's record is a 404 and never a leak. A path that matches no route at all answers 404 with the distinct code `unknown_endpoint` and echoes the path back, so a mistyped or half-built URL is told apart from a record that is missing or out of reach.

A record that is missing or out of reach Copy

 `{   "error": {     "code": "not_found",     "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."   } }`

A path that matches no endpoint Copy

 `{   "error": {     "code": "unknown_endpoint",     "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."   } }`

409 `default`

A uniqueness/record conflict; retry.

default Copy

 `{   "error": {     "code": "conflict",     "message": "تعذّر إتمام العملية بسبب تعارض. أعد المحاولة."   } }`

422 `default`

A caller-fixable bad request (validation error, bad date, bad enum).

default Copy

 `{   "error": {     "code": "unprocessable_entity",     "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."   } }`

429 `rate_limit_exceeded`

Either the company's daily API allowance is spent ( `rate_limit_exceeded` ), or the per-key / per-IP burst throttle of 300 requests per minute fired. Both come back after a wait, so `Retry-After` is honest here. **The two bodies are not the same shape.** The daily-quota refusal uses the standard error envelope. The burst throttle is served by Rack::Attack ahead of the application, so its body is a flat `{"error": "<string>"}` with no `code`. A client that reads `error.code` has to tolerate `error` being a plain string.

Today's daily allowance is spent (application envelope) Copy

 `{   "error": {     "code": "rate_limit_exceeded",     "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh."   } }`

Over 300 requests in a minute (Rack::Attack body, flat error) Copy

 `{   "error": "Rate limit exceeded. Please try again later." }`

[Errors](https://fareeqy.com/api/v1/docs/en#errors)

### Get a majlis topic by ref

GET /discussions/ {id} Requires `discussions:read`

#### Path parameters

`id` string required

The majlis topic REF (mjl-6khz2). A slug is not accepted here.

#### Request

cURL Copy

 `curl "https://app.fareeqy.com/api/v1/discussions/mjl-6khz2" \   -H "Authorization: Bearer $FAREEQY_API_KEY"`

#### Response

200 Copy

 `{   "data": {     "id": "mjl-6khz2",     "url": "https://app.fareeqy.com/majlis/mjl-6khz2",     "title": "اقتراح لتحسين صفحة التسعير",     "body": "أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة.",     "category": "فكرة",     "author": "سارة العتيبي",     "replies_count": 4,     "created_at": "2026-07-08T14:02:55.000+03:00"   } }`

Refusals: 401 403 404 429 Show failure examples Hide failure examples

401 `default`

Missing or invalid API key.

default Copy

 `{   "error": {     "code": "unauthorized",     "message": "مفتاح الـ API غير صالح أو غير مُرسَل."   } }`

403 `forbidden`

Two different refusals share this status, and a client must tell them apart by `error.code`. `forbidden` means the key's scope or allowlist does not permit this operation, or Pundit denied the action. `plan_upgrade_required` means the company's plan carries no API access at all, so no key on it can ever succeed and there is nothing to retry.

This key may not perform this operation Copy

 `{   "error": {     "code": "forbidden",     "message": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."   } }`

The company's plan carries no API access Copy

 `{   "error": {     "code": "plan_upgrade_required",     "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."   } }`

404 `not_found`

Resource not found or not accessible — also returned for EVERY endpoint when the company's `rest_api` feature flag is disabled (the surface is hidden). Lookups drill through the URL hierarchy, so another company's record is a 404 and never a leak. A path that matches no route at all answers 404 with the distinct code `unknown_endpoint` and echoes the path back, so a mistyped or half-built URL is told apart from a record that is missing or out of reach.

A record that is missing or out of reach Copy

 `{   "error": {     "code": "not_found",     "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."   } }`

A path that matches no endpoint Copy

 `{   "error": {     "code": "unknown_endpoint",     "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."   } }`

429 `rate_limit_exceeded`

Either the company's daily API allowance is spent ( `rate_limit_exceeded` ), or the per-key / per-IP burst throttle of 300 requests per minute fired. Both come back after a wait, so `Retry-After` is honest here. **The two bodies are not the same shape.** The daily-quota refusal uses the standard error envelope. The burst throttle is served by Rack::Attack ahead of the application, so its body is a flat `{"error": "<string>"}` with no `code`. A client that reads `error.code` has to tolerate `error` being a plain string.

Today's daily allowance is spent (application envelope) Copy

 `{   "error": {     "code": "rate_limit_exceeded",     "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh."   } }`

Over 300 requests in a minute (Rack::Attack body, flat error) Copy

 `{   "error": "Rate limit exceeded. Please try again later." }`

[Errors](https://fareeqy.com/api/v1/docs/en#errors)

### Update a majlis topic by ref

PATCH /discussions/ {id} Requires `discussions:write`

Only passed fields change.

#### Path parameters

`id` string required

The majlis topic REF (mjl-6khz2). A slug is not accepted here.

#### Request body

`title` string

`body` string | null

#### Request

cURL Copy

 `curl -X PATCH "https://app.fareeqy.com/api/v1/discussions/mjl-6khz2" \   -H "Authorization: Bearer $FAREEQY_API_KEY" \   -H "Content-Type: application/json" \   -d '{"body":"أقترح نعرض الباقات في جدول مقارنة واحد، ونثبت زر التجربة أعلى الصفحة."}'`

#### Response

200 Copy

 `{   "data": {     "id": "mjl-6khz2",     "url": "https://app.fareeqy.com/majlis/mjl-6khz2",     "title": "اقتراح لتحسين صفحة التسعير",     "body": "أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة.",     "category": "فكرة",     "author": "سارة العتيبي",     "replies_count": 4,     "created_at": "2026-07-08T14:02:55.000+03:00"   } }`

Refusals: 401 403 404 409 422 429 Show failure examples Hide failure examples

401 `default`

Missing or invalid API key.

default Copy

 `{   "error": {     "code": "unauthorized",     "message": "مفتاح الـ API غير صالح أو غير مُرسَل."   } }`

403 `forbidden`

Two different refusals share this status, and a client must tell them apart by `error.code`. `forbidden` means the key's scope or allowlist does not permit this operation, or Pundit denied the action. `plan_upgrade_required` means the company's plan carries no API access at all, so no key on it can ever succeed and there is nothing to retry.

This key may not perform this operation Copy

 `{   "error": {     "code": "forbidden",     "message": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."   } }`

The company's plan carries no API access Copy

 `{   "error": {     "code": "plan_upgrade_required",     "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."   } }`

404 `not_found`

Resource not found or not accessible — also returned for EVERY endpoint when the company's `rest_api` feature flag is disabled (the surface is hidden). Lookups drill through the URL hierarchy, so another company's record is a 404 and never a leak. A path that matches no route at all answers 404 with the distinct code `unknown_endpoint` and echoes the path back, so a mistyped or half-built URL is told apart from a record that is missing or out of reach.

A record that is missing or out of reach Copy

 `{   "error": {     "code": "not_found",     "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."   } }`

A path that matches no endpoint Copy

 `{   "error": {     "code": "unknown_endpoint",     "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."   } }`

409 `default`

A uniqueness/record conflict; retry.

default Copy

 `{   "error": {     "code": "conflict",     "message": "تعذّر إتمام العملية بسبب تعارض. أعد المحاولة."   } }`

422 `default`

A caller-fixable bad request (validation error, bad date, bad enum).

default Copy

 `{   "error": {     "code": "unprocessable_entity",     "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."   } }`

429 `rate_limit_exceeded`

Either the company's daily API allowance is spent ( `rate_limit_exceeded` ), or the per-key / per-IP burst throttle of 300 requests per minute fired. Both come back after a wait, so `Retry-After` is honest here. **The two bodies are not the same shape.** The daily-quota refusal uses the standard error envelope. The burst throttle is served by Rack::Attack ahead of the application, so its body is a flat `{"error": "<string>"}` with no `code`. A client that reads `error.code` has to tolerate `error` being a plain string.

Today's daily allowance is spent (application envelope) Copy

 `{   "error": {     "code": "rate_limit_exceeded",     "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh."   } }`

Over 300 requests in a minute (Rack::Attack body, flat error) Copy

 `{   "error": "Rate limit exceeded. Please try again later." }`

[Errors](https://fareeqy.com/api/v1/docs/en#errors)

### Delete a majlis topic by ref

DELETE /discussions/ {id} Requires `discussions:destructive`

SOFT delete (recoverable by an admin).

#### Path parameters

`id` string required

The majlis topic REF (mjl-6khz2). A slug is not accepted here.

#### Request

cURL Copy

 `curl -X DELETE "https://app.fareeqy.com/api/v1/discussions/mjl-6khz2" \   -H "Authorization: Bearer $FAREEQY_API_KEY"`

#### Response

200 Copy

 `{   "data": {     "deleted": true,     "recoverable": true,     "discussion": {       "title": "تسعير الباقات",       "id": "mjl-6khz2"     }   } }`

Refusals: 401 403 404 429 Show failure examples Hide failure examples

401 `default`

Missing or invalid API key.

default Copy

 `{   "error": {     "code": "unauthorized",     "message": "مفتاح الـ API غير صالح أو غير مُرسَل."   } }`

403 `forbidden`

Two different refusals share this status, and a client must tell them apart by `error.code`. `forbidden` means the key's scope or allowlist does not permit this operation, or Pundit denied the action. `plan_upgrade_required` means the company's plan carries no API access at all, so no key on it can ever succeed and there is nothing to retry.

This key may not perform this operation Copy

 `{   "error": {     "code": "forbidden",     "message": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."   } }`

The company's plan carries no API access Copy

 `{   "error": {     "code": "plan_upgrade_required",     "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."   } }`

404 `not_found`

Resource not found or not accessible — also returned for EVERY endpoint when the company's `rest_api` feature flag is disabled (the surface is hidden). Lookups drill through the URL hierarchy, so another company's record is a 404 and never a leak. A path that matches no route at all answers 404 with the distinct code `unknown_endpoint` and echoes the path back, so a mistyped or half-built URL is told apart from a record that is missing or out of reach.

A record that is missing or out of reach Copy

 `{   "error": {     "code": "not_found",     "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."   } }`

A path that matches no endpoint Copy

 `{   "error": {     "code": "unknown_endpoint",     "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."   } }`

429 `rate_limit_exceeded`

Either the company's daily API allowance is spent ( `rate_limit_exceeded` ), or the per-key / per-IP burst throttle of 300 requests per minute fired. Both come back after a wait, so `Retry-After` is honest here. **The two bodies are not the same shape.** The daily-quota refusal uses the standard error envelope. The burst throttle is served by Rack::Attack ahead of the application, so its body is a flat `{"error": "<string>"}` with no `code`. A client that reads `error.code` has to tolerate `error` being a plain string.

Today's daily allowance is spent (application envelope) Copy

 `{   "error": {     "code": "rate_limit_exceeded",     "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh."   } }`

Over 300 requests in a minute (Rack::Attack body, flat error) Copy

 `{   "error": "Rate limit exceeded. Please try again later." }`

[Errors](https://fareeqy.com/api/v1/docs/en#errors)

### Reply to a majlis topic by ref

POST /discussions/ {id} /reply Requires `discussions:write`

#### Path parameters

`id` string required

The majlis topic REF (mjl-6khz2). A slug is not accepted here.

#### Request body

`content` string required

Rich-text reply body (plain text in).

#### Request

cURL Copy

 `curl -X POST "https://app.fareeqy.com/api/v1/discussions/mjl-6khz2/reply" \   -H "Authorization: Bearer $FAREEQY_API_KEY" \   -H "Content-Type: application/json" \   -d '{"content":"أتفق. الجدول أوضح، بشرط نبقي الأسعار ظاهرة بدون ضغط إضافي."}'`

#### Response

201 Copy

 `{   "data": {     "id": 512,     "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",     "content": "راجعت النسخة الأخيرة، ينقصنا حالة الخطأ في النموذج.",     "author": "عبدالله المطيري",     "created_at": "2026-07-15T09:48:03.000+03:00",     "updated_at": "2026-07-15T09:48:03.000+03:00",     "attachments": [       {         "id": 9031,         "name": "error-state.png",         "content_type": "image/png",         "byte_size": 184320       }     ]   } }`

Refusals: 401 403 404 409 422 429 Show failure examples Hide failure examples

401 `default`

Missing or invalid API key.

default Copy

 `{   "error": {     "code": "unauthorized",     "message": "مفتاح الـ API غير صالح أو غير مُرسَل."   } }`

403 `forbidden`

Two different refusals share this status, and a client must tell them apart by `error.code`. `forbidden` means the key's scope or allowlist does not permit this operation, or Pundit denied the action. `plan_upgrade_required` means the company's plan carries no API access at all, so no key on it can ever succeed and there is nothing to retry.

This key may not perform this operation Copy

 `{   "error": {     "code": "forbidden",     "message": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."   } }`

The company's plan carries no API access Copy

 `{   "error": {     "code": "plan_upgrade_required",     "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."   } }`

404 `not_found`

Resource not found or not accessible — also returned for EVERY endpoint when the company's `rest_api` feature flag is disabled (the surface is hidden). Lookups drill through the URL hierarchy, so another company's record is a 404 and never a leak. A path that matches no route at all answers 404 with the distinct code `unknown_endpoint` and echoes the path back, so a mistyped or half-built URL is told apart from a record that is missing or out of reach.

A record that is missing or out of reach Copy

 `{   "error": {     "code": "not_found",     "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."   } }`

A path that matches no endpoint Copy

 `{   "error": {     "code": "unknown_endpoint",     "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."   } }`

409 `default`

A uniqueness/record conflict; retry.

default Copy

 `{   "error": {     "code": "conflict",     "message": "تعذّر إتمام العملية بسبب تعارض. أعد المحاولة."   } }`

422 `default`

A caller-fixable bad request (validation error, bad date, bad enum).

default Copy

 `{   "error": {     "code": "unprocessable_entity",     "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."   } }`

429 `rate_limit_exceeded`

Either the company's daily API allowance is spent ( `rate_limit_exceeded` ), or the per-key / per-IP burst throttle of 300 requests per minute fired. Both come back after a wait, so `Retry-After` is honest here. **The two bodies are not the same shape.** The daily-quota refusal uses the standard error envelope. The burst throttle is served by Rack::Attack ahead of the application, so its body is a flat `{"error": "<string>"}` with no `code`. A client that reads `error.code` has to tolerate `error` being a plain string.

Today's daily allowance is spent (application envelope) Copy

 `{   "error": {     "code": "rate_limit_exceeded",     "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh."   } }`

Over 300 requests in a minute (Rack::Attack body, flat error) Copy

 `{   "error": "Rate limit exceeded. Please try again later." }`

[Errors](https://fareeqy.com/api/v1/docs/en#errors)

[Previous Comments](https://fareeqy.com/api/v1/docs/comments/en) [Next Files](https://fareeqy.com/api/v1/docs/files/en)
