Discussions

Majlis discussion topics within a project. Slug ids.

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

The resource

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

idstring

The discussion slug.

titlestring
bodystring | null

Plain-text rendering of the rich-text body.

categorystring | null
authorstring | null
replies_countinteger
created_atstring (date-time)
Discussion
{
  "id": "اقتراح-لتحسين-صفحة",
  "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}/discussionsRequires discussions:read

Path parameters

project_idstringrequired

The project SLUG.

Query parameters

limitinteger

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

Default: 50

offsetinteger

Number of records to skip (default 0).

Default: 0

Request

cURL
curl "https://app.fareeqy.com/api/v1/projects/تطوير-الموقع/discussions" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": [
    {
      "id": "قرار-توحيد-هوية-التقارير",
      "title": "قرار: توحيد هوية التقارير المُصدَّرة",
      "body": "اتفقنا في اجتماع الاثنين على أن كل تقرير يخرج من فريقي يحمل هوية الشركة نفسها: الشعار في الترويسة، ولون العناوين #00674F، والتاريخ بالهجري والميلادي معاً في الصفحة الأولى.\n\nالسبب أن التقارير التي أرسلناها للمستثمرين الربع الماضي خرجت بثلاثة أشكال مختلفة لأن كل واحد منا صدّرها من شاشة مختلفة، فبدت وكأنها من ثلاث شركات.\n\nالتنفيذ على فريق التصميم قبل نهاية الشهر، والمرجع ملف الهوية المرفق في مجلد «الهوية البصرية». من عنده اعتراض فليكتبه هنا قبل الأحد، وبعدها يصير القرار نافذاً.",
      "category": "قرار",
      "author": "سارة العتيبي",
      "replies_count": 7,
      "created_at": "2026-07-21T09:12:44.000+03:00"
    },
    {
      "id": "اقتراح-لتحسين-صفحة",
      "title": "اقتراح لتحسين صفحة التسعير",
      "body": "أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة.\n\nلاحظت من تسجيلات الجلسات أن الزائر يفتح البطاقات الأربع واحدة بعد الأخرى ثم يعود للأعلى، وهذا يعني أنه يحاول المقارنة ولا تسعفه الواجهة. الجدول يحل هذا في نظرة واحدة.",
      "category": "فكرة",
      "author": "عبدالله المطيري",
      "replies_count": 4,
      "created_at": "2026-07-08T14:02:55.000+03:00"
    },
    {
      "id": "تنبيه-إجازة-فريق-التطوير",
      "title": "إجازة فريق التطوير الأسبوع القادم",
      "body": "فريق التطوير في إجازة من الأحد إلى الثلاثاء. أي طلب عاجل يُرفع هنا وسنتابعه، وما عدا ذلك يُجدول بعد العودة.",
      "category": "إعلان",
      "author": "نورة الحربي",
      "replies_count": 2,
      "created_at": "2026-07-02T08:30:10.000+03:00"
    }
  ],
  "meta": {
    "total": 18,
    "limit": 50,
    "offset": 0,
    "count": 3
  }
}
Refusals:401403404429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or missing API key."
  }
}
403forbidden

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
{
  "error": {
    "code": "forbidden",
    "message": "This API key is not permitted to perform this operation."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الانتاجي» لتفعيله. — The الاحترافي plan does not include API access. Upgrade to «المتطور» or «الانتاجي» to enable it."
  }
}
404default

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.

default
{
  "error": {
    "code": "not_found",
    "message": "Resource not found, or you do not have access to it."
  }
}
429rate_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)
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh. — Daily API quota exhausted: the المتطور plan allows 1000 calls per day. It resets at midnight Asia/Riyadh."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Create (and publish) a majlis topic

POST/projects/{project_id}/discussionsRequires discussions:write

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

Path parameters

project_idstringrequired

The project SLUG.

Request body

titlestringrequired
bodystring | null

Rich-text body (plain text in).

Request

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

Response

201
{
  "data": {
    "id": "اقتراح-لتحسين-صفحة",
    "title": "اقتراح لتحسين صفحة التسعير",
    "body": "أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة.",
    "category": "فكرة",
    "author": "سارة العتيبي",
    "replies_count": 4,
    "created_at": "2026-07-08T14:02:55.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or missing API key."
  }
}
403forbidden

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
{
  "error": {
    "code": "forbidden",
    "message": "This API key is not permitted to perform this operation."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الانتاجي» لتفعيله. — The الاحترافي plan does not include API access. Upgrade to «المتطور» or «الانتاجي» to enable it."
  }
}
404default

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.

default
{
  "error": {
    "code": "not_found",
    "message": "Resource not found, or you do not have access to it."
  }
}
409default

A uniqueness/record conflict; retry.

default
{
  "error": {
    "code": "conflict",
    "message": "Could not complete due to a conflict; please retry."
  }
}
422default

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "Title can't be blank"
  }
}
429rate_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)
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh. — Daily API quota exhausted: the المتطور plan allows 1000 calls per day. It resets at midnight Asia/Riyadh."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Get a majlis topic

GET/projects/{project_id}/discussions/{id}Requires discussions:read

Path parameters

project_idstringrequired

The project SLUG.

idstringrequired

The discussion SLUG (unique within its project).

Request

cURL
curl "https://app.fareeqy.com/api/v1/projects/تطوير-الموقع/discussions/اقتراح-لتحسين-صفحة" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "id": "اقتراح-لتحسين-صفحة",
    "title": "اقتراح لتحسين صفحة التسعير",
    "body": "أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة.",
    "category": "فكرة",
    "author": "سارة العتيبي",
    "replies_count": 4,
    "created_at": "2026-07-08T14:02:55.000+03:00"
  }
}
Refusals:401403404429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or missing API key."
  }
}
403forbidden

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
{
  "error": {
    "code": "forbidden",
    "message": "This API key is not permitted to perform this operation."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الانتاجي» لتفعيله. — The الاحترافي plan does not include API access. Upgrade to «المتطور» or «الانتاجي» to enable it."
  }
}
404default

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.

default
{
  "error": {
    "code": "not_found",
    "message": "Resource not found, or you do not have access to it."
  }
}
429rate_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)
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh. — Daily API quota exhausted: the المتطور plan allows 1000 calls per day. It resets at midnight Asia/Riyadh."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Update a majlis topic

PATCH/projects/{project_id}/discussions/{id}Requires discussions:write

Path parameters

project_idstringrequired

The project SLUG.

idstringrequired

The discussion SLUG (unique within its project).

Request body

titlestring
bodystring | null

Request

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

Response

200
{
  "data": {
    "id": "اقتراح-لتحسين-صفحة",
    "title": "اقتراح لتحسين صفحة التسعير",
    "body": "أقترح نعرض الباقات في جدول مقارنة واحد بدل البطاقات المنفصلة.",
    "category": "فكرة",
    "author": "سارة العتيبي",
    "replies_count": 4,
    "created_at": "2026-07-08T14:02:55.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or missing API key."
  }
}
403forbidden

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
{
  "error": {
    "code": "forbidden",
    "message": "This API key is not permitted to perform this operation."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الانتاجي» لتفعيله. — The الاحترافي plan does not include API access. Upgrade to «المتطور» or «الانتاجي» to enable it."
  }
}
404default

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.

default
{
  "error": {
    "code": "not_found",
    "message": "Resource not found, or you do not have access to it."
  }
}
409default

A uniqueness/record conflict; retry.

default
{
  "error": {
    "code": "conflict",
    "message": "Could not complete due to a conflict; please retry."
  }
}
422default

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "Title can't be blank"
  }
}
429rate_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)
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh. — Daily API quota exhausted: the المتطور plan allows 1000 calls per day. It resets at midnight Asia/Riyadh."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Delete a majlis topic

DELETE/projects/{project_id}/discussions/{id}Requires discussions:destructive

SOFT delete (recoverable by an admin).

Path parameters

project_idstringrequired

The project SLUG.

idstringrequired

The discussion SLUG (unique within its project).

Request

cURL
curl -X DELETE "https://app.fareeqy.com/api/v1/projects/تطوير-الموقع/discussions/اقتراح-لتحسين-صفحة" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "deleted": true,
    "recoverable": true,
    "discussion": {
      "title": "اقتراح لتحسين صفحة التسعير",
      "slug": "اقتراح-لتحسين-صفحة"
    }
  }
}
Refusals:401403404429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or missing API key."
  }
}
403forbidden

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
{
  "error": {
    "code": "forbidden",
    "message": "This API key is not permitted to perform this operation."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الانتاجي» لتفعيله. — The الاحترافي plan does not include API access. Upgrade to «المتطور» or «الانتاجي» to enable it."
  }
}
404default

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.

default
{
  "error": {
    "code": "not_found",
    "message": "Resource not found, or you do not have access to it."
  }
}
429rate_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)
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh. — Daily API quota exhausted: the المتطور plan allows 1000 calls per day. It resets at midnight Asia/Riyadh."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Reply to a majlis topic

POST/projects/{project_id}/discussions/{id}/replyRequires discussions:write

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

Path parameters

project_idstringrequired

The project SLUG.

idstringrequired

The discussion SLUG (unique within its project).

Request body

contentstringrequired

Rich-text reply body (plain text in).

Request

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

Response

201
{
  "data": {
    "id": 512,
    "content": "راجعت النسخة الأخيرة، ينقصنا حالة الخطأ في النموذج.",
    "author": "عبدالله المطيري",
    "created_at": "2026-07-15T09:48:03.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or missing API key."
  }
}
403forbidden

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
{
  "error": {
    "code": "forbidden",
    "message": "This API key is not permitted to perform this operation."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الانتاجي» لتفعيله. — The الاحترافي plan does not include API access. Upgrade to «المتطور» or «الانتاجي» to enable it."
  }
}
404default

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.

default
{
  "error": {
    "code": "not_found",
    "message": "Resource not found, or you do not have access to it."
  }
}
409default

A uniqueness/record conflict; retry.

default
{
  "error": {
    "code": "conflict",
    "message": "Could not complete due to a conflict; please retry."
  }
}
422default

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "Title can't be blank"
  }
}
429rate_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)
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh. — Daily API quota exhausted: the المتطور plan allows 1000 calls per day. It resets at midnight Asia/Riyadh."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors