Tasks

The actionable items nested under a task list. Ref ids (tsk-); a slug still resolves.

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

The resource

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

idstring

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

urlstring | null

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

titlestring
notesstring | null

Plain-text rendering of the rich-text notes.

completedboolean
completed_atstring (date-time) | null
due_atstring (date) | null

Due DATE (no time component).

time_spent_minutesinteger | null

How long the task took, in whole minutes. Always present, null when nobody has measured it — null is not zero. Reading it is ungated; writing needs a plan that carries time tracking.

project_slugstring

The project ref. Named _slug for compatibility.

task_list_slugstring

The task-list ref. Named _slug for compatibility.

assigneeobject | any | null
created_atstring (date-time)
Task
{
  "id": "tsk-9wb7t",
  "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
  "title": "تصميم الهيدر",
  "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
  "completed": false,
  "completed_at": null,
  "due_at": "2026-08-10",
  "time_spent_minutes": 90,
  "project_slug": "prj-8f3kd",
  "task_list_slug": "lst-4m2qp",
  "assignee": {
    "name": "عبدالله المطيري",
    "email": "abdullah@example.com"
  },
  "created_at": "2026-07-03T11:40:09.000+03:00"
}

List a task list's tasks

GET/projects/{project_id}/lists/{task_list_id}/tasksRequires tasks:read

Path parameters

project_idstringrequired

The project REF, or its slug.

task_list_idstringrequired

The task-list REF, or its 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/prj-8f3kd/lists/lst-4m2qp/tasks" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": [
    {
      "id": "tsk-9wb7t",
      "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
      "title": "تصميم الهيدر",
      "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
      "completed": false,
      "completed_at": null,
      "due_at": "2026-08-10",
      "time_spent_minutes": 90,
      "project_slug": "prj-8f3kd",
      "task_list_slug": "lst-4m2qp",
      "assignee": {
        "name": "عبدالله المطيري",
        "email": "abdullah@example.com"
      },
      "created_at": "2026-07-03T11:40:09.000+03:00"
    }
  ],
  "meta": {
    "total": 4,
    "limit": 50,
    "offset": 0,
    "count": 4
  }
}
Refusals:401403404429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Create a task

POST/projects/{project_id}/lists/{task_list_id}/tasksRequires tasks:write

Creates a task in the list. assignee_email must name an assignable member (see the project's members).

Path parameters

project_idstringrequired

The project REF, or its slug.

task_list_idstringrequired

The task-list REF, or its slug.

Request body

titlestringrequired
notesstring | null

Rich-text notes (plain text in).

due_atstring | null

Due DATE (YYYY-MM-DD, e.g. 2026-07-20). A task is due on a day, not at a moment: the column is a date, so a value carrying a time is accepted and the time is then dropped. Send 2026-07-20T14:00 and you get 2026-07-20 back.

assignee_emailstring (email) | null

Email of an assignable member.

Request

cURL
curl -X POST "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/lists/lst-4m2qp/tasks" \
  -H "Authorization: Bearer $FAREEQY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"تصميم الهيدر","notes":"نحتاج نسخة للجوال ونسخة للديسكتوب.","due_at":"2026-08-10","assignee_email":"abdullah@example.com"}'

Response

201
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

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

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Get a task

GET/projects/{project_id}/lists/{task_list_id}/tasks/{id}Requires tasks:read

Path parameters

project_idstringrequired

The project REF, or its slug.

task_list_idstringrequired

The task-list REF, or its slug.

idstringrequired

The task REF (tsk-8f3kd), or its slug (unique within its task list).

Request

cURL
curl "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/lists/lst-4m2qp/tasks/tsk-9wb7t" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Update a task

PATCH/projects/{project_id}/lists/{task_list_id}/tasks/{id}Requires tasks:write

Only passed fields change. Passing assignee_email (even empty) reassigns; an empty value unassigns.

Path parameters

project_idstringrequired

The project REF, or its slug.

task_list_idstringrequired

The task-list REF, or its slug.

idstringrequired

The task REF (tsk-8f3kd), or its slug (unique within its task list).

Request body

titlestring
notesstring | null
due_atstring | null

New due DATE (YYYY-MM-DD). Any time component is dropped — see the create operation.

assignee_emailstring | null

Email of an assignable member. An empty string unassigns, which is why this carries no email format: the empty value is a real, accepted input here.

Request

cURL
curl -X PATCH "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/lists/lst-4m2qp/tasks/tsk-9wb7t" \
  -H "Authorization: Bearer $FAREEQY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"","notes":null,"due_at":null,"assignee_email":null}'

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

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

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Delete a task

DELETE/projects/{project_id}/lists/{task_list_id}/tasks/{id}Requires tasks:destructive

Permanent.

Path parameters

project_idstringrequired

The project REF, or its slug.

task_list_idstringrequired

The task-list REF, or its slug.

idstringrequired

The task REF (tsk-8f3kd), or its slug (unique within its task list).

Request

cURL
curl -X DELETE "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/lists/lst-4m2qp/tasks/tsk-9wb7t" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "deleted": true,
    "task": {
      "title": "تصميم الهيدر",
      "id": "tsk-9wb7t"
    }
  }
}
Refusals:401403404429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Mark a task complete

POST/projects/{project_id}/lists/{task_list_id}/tasks/{id}/completeRequires tasks:write

Idempotent: completing an already-complete task is a no-op.

Path parameters

project_idstringrequired

The project REF, or its slug.

task_list_idstringrequired

The task-list REF, or its slug.

idstringrequired

The task REF (tsk-8f3kd), or its slug (unique within its task list).

Request

cURL
curl -X POST "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/lists/lst-4m2qp/tasks/tsk-9wb7t/complete" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

default
{
  "error": {
    "code": "conflict",
    "message": "تعذّر إتمام العملية بسبب تعارض. أعد المحاولة."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Reopen a task

POST/projects/{project_id}/lists/{task_list_id}/tasks/{id}/incompleteRequires tasks:write

Idempotent.

Path parameters

project_idstringrequired

The project REF, or its slug.

task_list_idstringrequired

The task-list REF, or its slug.

idstringrequired

The task REF (tsk-8f3kd), or its slug (unique within its task list).

Request

cURL
curl -X POST "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/lists/lst-4m2qp/tasks/tsk-9wb7t/incomplete" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

default
{
  "error": {
    "code": "conflict",
    "message": "تعذّر إتمام العملية بسبب تعارض. أعد المحاولة."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Move a task to another list in the same project

POST/projects/{project_id}/lists/{task_list_id}/tasks/{id}/moveRequires tasks:write

Moves the task to a different list in the SAME project, appended to the end. The destination is resolved inside the project in the path, so target_task_list_slug may be its ref, slug or numeric id. To move a task into another project, use the flat address.

Path parameters

project_idstringrequired

The project REF, or its slug.

task_list_idstringrequired

The task-list REF, or its slug.

idstringrequired

The task REF (tsk-8f3kd), or its slug (unique within its task list).

Request body

target_task_list_slugstringrequired

The destination task list. On the nested address, its ref, slug or numeric id inside the same project. On the flat address, its ref only, and it may be in another project. Named _slug for compatibility.

Request

cURL
curl -X POST "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/lists/lst-4m2qp/tasks/tsk-9wb7t/move" \
  -H "Authorization: Bearer $FAREEQY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target_task_list_slug":"lst-7hx2r"}'

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

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

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Record time spent on a task

POST/projects/{project_id}/lists/{task_list_id}/tasks/{id}/timeRequires tasks:write

Records how long the task took. A separate action from PATCH, as it is in the web app. , and a plan that carries time tracking (403 otherwise, with nothing recorded).

Path parameters

project_idstringrequired

The project REF, or its slug.

task_list_idstringrequired

The task-list REF, or its slug.

idstringrequired

The task REF (tsk-8f3kd), or its slug (unique within its task list).

Request

cURL
curl -X POST "https://app.fareeqy.com/api/v1/projects/prj-8f3kd/lists/lst-4m2qp/tasks/tsk-9wb7t/time" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

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

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

List a task list's tasks, by ref

GET/lists/{task_list_id}/tasksRequires tasks:read

Path parameters

task_list_idstringrequired

The task list REF (lst-4m2qp). A slug is not accepted here.

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/lists/lst-4m2qp/tasks" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": [
    {
      "id": "tsk-9wb7t",
      "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
      "title": "تصميم الهيدر",
      "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
      "completed": false,
      "completed_at": null,
      "due_at": "2026-08-10",
      "time_spent_minutes": 90,
      "project_slug": "prj-8f3kd",
      "task_list_slug": "lst-4m2qp",
      "assignee": {
        "name": "عبدالله المطيري",
        "email": "abdullah@example.com"
      },
      "created_at": "2026-07-03T11:40:09.000+03:00"
    }
  ],
  "meta": {
    "total": 4,
    "limit": 50,
    "offset": 0,
    "count": 4
  }
}
Refusals:401403404429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Create a task in a task list, by ref

POST/lists/{task_list_id}/tasksRequires tasks:write

Path parameters

task_list_idstringrequired

The task list REF (lst-4m2qp). A slug is not accepted here.

Request body

titlestringrequired
notesstring | null

Rich-text notes (plain text in).

due_atstring | null

Due DATE (YYYY-MM-DD, e.g. 2026-07-20). A task is due on a day, not at a moment: the column is a date, so a value carrying a time is accepted and the time is then dropped. Send 2026-07-20T14:00 and you get 2026-07-20 back.

assignee_emailstring (email) | null

Email of an assignable member.

Request

cURL
curl -X POST "https://app.fareeqy.com/api/v1/lists/lst-4m2qp/tasks" \
  -H "Authorization: Bearer $FAREEQY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"تصميم الهيدر","notes":"نحتاج نسخة للجوال ونسخة للديسكتوب.","due_at":"2026-08-10","assignee_email":"abdullah@example.com"}'

Response

201
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

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

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Get a task by ref

GET/tasks/{id}Requires tasks:read

The shortest address this API has, and the one a person copies out of the browser.

Path parameters

idstringrequired

The task REF (tsk-9wb7t). A slug is not accepted here.

Request

cURL
curl "https://app.fareeqy.com/api/v1/tasks/tsk-9wb7t" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Update a task by ref

PATCH/tasks/{id}Requires tasks:write

Only passed fields change. Passing assignee_email (even empty) reassigns; an empty value unassigns.

Path parameters

idstringrequired

The task REF (tsk-9wb7t). A slug is not accepted here.

Request body

titlestring
notesstring | null
due_atstring | null

New due DATE (YYYY-MM-DD). Any time component is dropped — see the create operation.

assignee_emailstring | null

Email of an assignable member. An empty string unassigns, which is why this carries no email format: the empty value is a real, accepted input here.

Request

cURL
curl -X PATCH "https://app.fareeqy.com/api/v1/tasks/tsk-9wb7t" \
  -H "Authorization: Bearer $FAREEQY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"","notes":null,"due_at":null,"assignee_email":null}'

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

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

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Delete a task by ref

DELETE/tasks/{id}Requires tasks:destructive

Permanent.

Path parameters

idstringrequired

The task REF (tsk-9wb7t). A slug is not accepted here.

Request

cURL
curl -X DELETE "https://app.fareeqy.com/api/v1/tasks/tsk-9wb7t" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "deleted": true,
    "task": {
      "title": "تصميم الهيدر",
      "id": "tsk-9wb7t"
    }
  }
}
Refusals:401403404429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Complete a task by ref

POST/tasks/{id}/completeRequires tasks:write

Idempotent.

Path parameters

idstringrequired

The task REF (tsk-9wb7t). A slug is not accepted here.

Request

cURL
curl -X POST "https://app.fareeqy.com/api/v1/tasks/tsk-9wb7t/complete" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

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

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Reopen a task by ref

POST/tasks/{id}/incompleteRequires tasks:write

Idempotent.

Path parameters

idstringrequired

The task REF (tsk-9wb7t). A slug is not accepted here.

Request

cURL
curl -X POST "https://app.fareeqy.com/api/v1/tasks/tsk-9wb7t/incomplete" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

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

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Move a task to another list, by ref

POST/tasks/{id}/moveRequires tasks:write

Moves the task to another task list, appended to the end. The destination may be in another project of the company. With no project in the path, target_task_list_slug must be the list's ref (lst-4m2qp): a slug is not accepted here.

Path parameters

idstringrequired

The task REF (tsk-9wb7t). A slug is not accepted here.

Request body

target_task_list_slugstringrequired

The destination task list. On the nested address, its ref, slug or numeric id inside the same project. On the flat address, its ref only, and it may be in another project. Named _slug for compatibility.

Request

cURL
curl -X POST "https://app.fareeqy.com/api/v1/tasks/tsk-9wb7t/move" \
  -H "Authorization: Bearer $FAREEQY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target_task_list_slug":"lst-7hx2r"}'

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

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

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors

Record time spent on a task, by ref

POST/tasks/{id}/timeRequires tasks:write

Records how long the task took. A separate action from PATCH, as it is in the web app. , and a plan that carries time tracking (403 otherwise, with nothing recorded).

Path parameters

idstringrequired

The task REF (tsk-9wb7t). A slug is not accepted here.

Request

cURL
curl -X POST "https://app.fareeqy.com/api/v1/tasks/tsk-9wb7t/time" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "id": "tsk-9wb7t",
    "url": "https://app.fareeqy.com/tasks/tsk-9wb7t",
    "title": "تصميم الهيدر",
    "notes": "نحتاج نسخة للجوال ونسخة للديسكتوب.",
    "completed": false,
    "completed_at": null,
    "due_at": "2026-08-10",
    "time_spent_minutes": 90,
    "project_slug": "prj-8f3kd",
    "task_list_slug": "lst-4m2qp",
    "assignee": {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    },
    "created_at": "2026-07-03T11:40:09.000+03:00"
  }
}
Refusals:401403404409422429Show failure examples
401default

Missing or invalid API key.

default
{
  "error": {
    "code": "unauthorized",
    "message": "مفتاح الـ API غير صالح أو غير مُرسَل."
  }
}
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": "هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية."
  }
}
The company's plan carries no API access
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله."
  }
}
404not_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
{
  "error": {
    "code": "not_found",
    "message": "غير موجود، أو لا تملك صلاحية الوصول إليه."
  }
}
A path that matches no endpoint
{
  "error": {
    "code": "unknown_endpoint",
    "message": "لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API."
  }
}
409default

A uniqueness/record conflict; retry.

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

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

default
{
  "error": {
    "code": "unprocessable_entity",
    "message": "قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل."
  }
}
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."
  }
}
Over 300 requests in a minute (Rack::Attack body, flat error)
{
  "error": "Rate limit exceeded. Please try again later."
}
Errors