Fareeqy Projects API
The company's projects, private ones included.
The company's projects, private ones included.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/fareeqy-com:fareeqy-com-projects-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Fareeqy REST Projects API
version: 1.3.0
summary: Authenticated public REST API for Fareeqy project management.
description: 'The Fareeqy public REST API (`/api/v1`) lets a company automate its projects,
task lists, tasks, majlis discussions, files, folders and calendar events over
plain JSON with a Bearer API key. **Preview.** This surface is gated behind the per-company Flipper feature flag
`rest_api` (off by default). While the flag is disabled for a company, every
authenticated endpoint returns **404** — the surface is hidden, not merely
forbidden. Authentication is checked first (a bad key is still 401), then the
flag, so the 404 only appears once a valid key has been presented. With the flag on, the PLAN decides access: Free and Pro carry no API calls, so
a key on either is refused **403** `plan_upgrade_required`. There is deliberately no "my tasks" or "my personal tasks" endpoint. A key
belongs to the COMPANY and the person on it merely created it, so "assigned
to me" has no answer a company credential can give. Both lists remain on the
MCP server, whose Personal Access Token really is personal. ## Vocabulary
Product vocabulary and the internal schema differ, and this API follows the
**product**:
| API term | Path segment | What it is |
|---|---|---|
| Task list | `/lists` | A container that groups tasks within a project (schema model `Task`) |
| Task | `/tasks` | The actionable item a user checks off (schema model `Todo`) |
| Majlis topic / discussion | `/discussions` | A project discussion thread |
## Identifiers
Every nested path segment accepts **any** shape below: the ref, the slug, or
the record''s numeric id. The flat addresses take the ref only. Reads hand back
the ref, so a client that follows its own output stays on refs without
deciding anything. - **Refs** are the current identifier: a short ASCII handle like `prj-8f3kd`,
`lst-4m2qp`, `tsk-9wb7t`, `mjl-6khz2`, `fil-3qd8n` — the same string the
فريقي web app shows in its address bar, so a person can paste a link
straight into a script. Three properties are worth knowing: a ref never
changes when the record is renamed; it needs no percent-encoding; and the
prefix is checked, so `/lists/tsk-…` is a 404 rather than a wrong
record of the wrong kind. This is what the `id` field of every project,
task list, task and discussion response contains, and the `id` of a file
hit in search. A file response itself carries its numeric id. - **Slugs** are the older identifier and keep working on every nested
address, including slugs retired by a rename. The flat addresses refuse them. A slug is unique only **within its parent**, so
URLs stay fully nested and every slug is resolved through its parent (a
task-list slug can repeat across projects). Never assume a slug is globally
unique, and remember an Arabic slug must be percent-encoded. - **Numeric ids** address the records that have no ref: folders, events,
comments, and the files attached inside a comment. A file accepts both its
`fil-` ref and its numeric id on the nested routes, and projects, task
lists, tasks and discussions accept their numeric id there too. These ids
are global, so the API always drills a lookup through the parent project
(or, for events, the key''s company): a numeric id from another company
resolves to 404, never a leak. Parameters are still **named** `*_slug` in the paths and the schemas below. That is compatibility, not a constraint: renaming a public parameter would
break every caller, and widening what one accepts breaks nobody. Read them as
"handle". The same holds for the `*_slug` fields in responses (`project_slug`,
`task_list_slug`, `task_slug`, `discussion_slug`): they carry the ref, not a
slug. ## Links
Every project, task list, task, discussion, comment, file, folder, event and
search hit carries `url`: the absolute address of the page a person opens in
فريقي. A comment has no page of its own, so its `url` is its parent''s page; a
folder''s is the files page scoped to that folder. A project member is the one
record without a `url`. Link to it rather than building the address yourself.'
x-operation-catalog:
description: 'The full operation catalog (Api::OperationRegistry). read needs the read scope; write needs the write scope; destructive needs the write scope AND is excluded from the write preset''s default grant (opt-in per key). `account` has no destructive operation.
'
account:
read:
- account:read
projects:
read:
- projects:read
write:
- projects:write
destructive:
- projects:destructive
task_lists:
read:
- task_lists:read
write:
- task_lists:write
destructive:
- task_lists:destructive
tasks:
read:
- tasks:read
write:
- tasks:write
destructive:
- tasks:destructive
discussions:
read:
- discussions:read
write:
- discussions:write
destructive:
- discussions:destructive
comments:
read:
- comments:read
write:
- comments:write
files:
read:
- files:read
write:
- files:write
destructive:
- files:destructive
events:
read:
- events:read
write:
- events:write
destructive:
- events:destructive
servers:
- url: https://app.fareeqy.com/api/v1
description: Production. The app runs on app.fareeqy.com (fareeqy.com is the marketing site and answers no API call). The base path (/api/v1) is part of the server URL, so operation paths below are written relative to it (e.g. /projects).
security:
- bearerAuth: []
tags:
- name: Projects
description: The company's projects, private ones included.
paths:
/projects:
get:
tags:
- Projects
operationId: listProjects
summary: List the company's projects
description: 'Every project in the key''s company, private ones included, newest first.
The one exception is an onboarding demo project, which stays private to the
person it was created for. Excludes archived unless `include_archived`.
Requires operation `projects:read`.'
parameters:
- name: include_archived
in: query
description: Include archived projects.
schema:
type: boolean
default: false
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/Offset'
x-codeSamples:
- lang: cURL
label: curl
source: "curl -s \"$FRQ_BASE/projects?limit=20&offset=0&include_archived=false\" \\\n -H \"Authorization: Bearer $FRQ_KEY\"\n"
responses:
'200':
description: A page of projects.
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/CollectionMeta'
- type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Project'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
post:
tags:
- Projects
operationId: createProject
summary: Create a project
description: Requires operation `projects:write`.
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
description:
type:
- string
- 'null'
start_date:
type:
- string
- 'null'
description: ISO 8601 date.
end_date:
type:
- string
- 'null'
description: ISO 8601 date.
is_public:
type: boolean
example:
name: تطوير الموقع
description: إعادة بناء الموقع التعريفي وربطه بالمنتج.
start_date: '2026-07-01'
end_date: '2026-09-30'
is_public: false
x-codeSamples:
- lang: cURL
label: curl
source: "curl -s -X POST \"$FRQ_BASE/projects\" \\\n -H \"Authorization: Bearer $FRQ_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Website launch\",\n \"description\": \"Everything before we ship\",\n \"start_date\": \"2026-08-05\",\n \"end_date\": \"2026-09-30\",\n \"is_public\": true\n }'\n"
responses:
'201':
description: The created project.
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectEnvelope'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
/projects/{id}:
parameters:
- $ref: '#/components/parameters/ProjectSlugId'
get:
tags:
- Projects
operationId: getProject
summary: Get a project
description: Requires operation `projects:read`.
x-codeSamples:
- lang: cURL
label: curl
source: "# $PROJECT is the ref returned as `data.id` on create (prj-8f3kd).\n# A slug still works here, but needs percent-encoding when it is Arabic.\ncurl -s \"$FRQ_BASE/projects/$PROJECT\" \\\n -H \"Authorization: Bearer $FRQ_KEY\"\n"
responses:
'200':
description: The project.
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectEnvelope'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
patch:
tags:
- Projects
operationId: updateProject
summary: Update a project
description: Requires operation `projects:write`.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
name:
type: string
description:
type:
- string
- 'null'
start_date:
type:
- string
- 'null'
end_date:
type:
- string
- 'null'
is_public:
type: boolean
example:
description: إعادة بناء الموقع التعريفي وربطه بالمنتج وبلوحة القياس.
end_date: '2026-10-15'
x-codeSamples:
- lang: cURL
label: curl
source: "curl -s -X PATCH \"$FRQ_BASE/projects/$PROJECT\" \\\n -H \"Authorization: Bearer $FRQ_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"description\": \"Scope agreed with the client\"}'\n"
responses:
'200':
description: The updated project.
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectEnvelope'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
delete:
tags:
- Projects
operationId: deleteProject
summary: Delete a project
description: 'Permanent, cascading delete. Prefer archive. The response reports the
counts destroyed. A project that a connected integration feeds is refused
with 409 `project_in_use` until the integration is disconnected or pointed
at another project. Requires operation `projects:destructive`.'
responses:
'200':
description: Deletion result.
content:
application/json:
schema:
type: object
properties:
data:
allOf:
- $ref: '#/components/schemas/DeletedBase'
- type: object
properties:
project:
type: object
properties:
name:
type: string
id:
type: string
description: The project ref.
destroyed:
type: object
properties:
task_lists:
type: integer
tasks:
type: integer
discussions:
type: integer
files:
type: integer
folders:
type: integer
example:
data:
deleted: true
project:
name: تطوير الموقع
id: prj-8f3kd
destroyed:
task_lists: 4
tasks: 27
discussions: 3
files: 12
folders: 2
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/ProjectInUse'
'429':
$ref: '#/components/responses/RateLimited'
/projects/{id}/archive:
parameters:
- $ref: '#/components/parameters/ProjectSlugId'
post:
tags:
- Projects
operationId: archiveProject
summary: Archive a project
description: 'Reversible put-away; records a timeline event and freezes writes across the
project. Prefer this over delete. Requires operation `projects:write`.'
x-codeSamples:
- lang: cURL
label: curl
source: "# The reversible put-away. Prefer it to DELETE, which cascades permanently.\ncurl -s -X POST \"$FRQ_BASE/projects/$PROJECT/archive\" \\\n -H \"Authorization: Bearer $FRQ_KEY\"\n"
responses:
'200':
description: The archived project.
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectEnvelope'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
/projects/{id}/unarchive:
parameters:
- $ref: '#/components/parameters/ProjectSlugId'
post:
tags:
- Projects
operationId: unarchiveProject
summary: Unarchive a project
description: Requires operation `projects:write`.
responses:
'200':
description: The reactivated project.
content:
application/json:
schema:
$ref: '#/components/schemas/ProjectEnvelope'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
components:
responses:
Conflict:
description: A uniqueness/record conflict; retry.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: conflict
message: تعذّر إتمام العملية بسبب تعارض. أعد المحاولة.
Forbidden:
description: '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.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
forbidden:
summary: This key may not perform this operation
value:
error:
code: forbidden
message: هذا الـ API key لا يملك صلاحية تنفيذ هذه العملية.
plan_upgrade_required:
summary: The company's plan carries no API access
value:
error:
code: plan_upgrade_required
message: خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الإنتاجي» لتفعيله.
Unprocessable:
description: A caller-fixable bad request (validation error, bad date, bad enum).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: unprocessable_entity
message: قيمة due_at غير صالحة؛ أرسل تاريخاً مثل 2026-07-20. المهمة تُستحق في يوم، وأي وقت ترسله يُهمل.
ProjectInUse:
description: 'The project cannot be deleted because a connected integration sends its work there. Nothing was deleted. Disconnect the integration, or point it at another project, then delete again. Retrying as-is will not help.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: project_in_use
message: هذا المشروع وجهة تكامل موصول (Sentry). حذفه لا يوقف وصول الأخطاء، بل يجعلها تصل إلى لا مكان. افصل التكامل من «الإعدادات ← التكاملات»، أو وجّهه إلى مشروع آخر، ثم احذف هذا المشروع.
NotFound:
description: '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.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
not_found:
summary: A record that is missing or out of reach
value:
error:
code: not_found
message: غير موجود، أو لا تملك صلاحية الوصول إليه.
unknown_endpoint:
summary: A path that matches no endpoint
value:
error:
code: unknown_endpoint
message: لا يوجد endpoint يطابق GET /api/v1/projcts. راجع المسار في توثيق الـ API.
RateLimited:
description: '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.
'
headers:
Retry-After:
description: Seconds to wait before retrying. On a spent daily allowance this points at the company's next midnight.
schema:
type: integer
X-RateLimit-Limit:
description: The plan's daily API allowance, or `unlimited`.
schema:
type: string
X-RateLimit-Remaining:
description: Calls left in today's allowance, or `unlimited`.
schema:
type: string
X-RateLimit-Reset:
description: Unix timestamp of the next reset (the company's next midnight).
schema:
type: integer
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/Error'
- type: object
description: The Rack::Attack burst-throttle body. Flat, with no error code.
required:
- error
properties:
error:
type: string
examples:
rate_limit_exceeded:
summary: Today's daily allowance is spent (application envelope)
value:
error:
code: rate_limit_exceeded
message: استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh.
burst_throttled:
summary: Over 300 requests in a minute (Rack::Attack body, flat error)
value:
error: Rate limit exceeded. Please try again later.
Unauthorized:
description: Missing or invalid API key.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: unauthorized
message: مفتاح الـ API غير صالح أو غير مُرسَل.
parameters:
Limit:
name: limit
in: query
description: Page size (default 50, max 100, clamped).
schema:
type: integer
default: 50
minimum: 1
maximum: 100
Offset:
name: offset
in: query
description: Number of records to skip (default 0).
schema:
type: integer
default: 0
minimum: 0
ProjectSlugId:
name: id
in: path
required: true
description: The project REF (prj-8f3kd), or its slug (unique within the company).
schema:
type: string
schemas:
Error:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
description: A stable machine-readable slug.
enum:
- unauthorized
- forbidden
- plan_upgrade_required
- not_found
- unknown_endpoint
- unprocessable_entity
- conflict
- project_in_use
- bad_request
- rate_limit_exceeded
message:
type: string
description: A human-readable explanation in Arabic (may join several validation messages with "; ").
Project:
type: object
properties:
id:
type: string
description: 'The project ref: the stable public id. A slug is still accepted anywhere this is passed back.'
url:
type:
- string
- 'null'
description: Absolute address of the page a person opens in فريقي.
name:
type: string
description:
type:
- string
- 'null'
is_public:
type: boolean
archived:
type: boolean
start_date:
type:
- string
- 'null'
format: date
end_date:
type:
- string
- 'null'
format: date
task_lists_count:
type: integer
description: Number of task lists (counter cache).
tasks_count:
type: integer
description: Number of tasks across the project's lists.
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
example:
id: prj-8f3kd
url: https://app.fareeqy.com/projects/prj-8f3kd
name: تطوير الموقع
description: إعادة بناء الموقع التعريفي وربطه بالمنتج.
is_public: false
archived: false
start_date: '2026-07-01'
end_date: '2026-09-30'
task_lists_count: 4
tasks_count: 27
created_at: '2026-07-01T09:15:00.000+03:00'
updated_at: '2026-07-14T12:22:31.000+03:00'
CollectionMeta:
type: object
properties:
meta:
type: object
properties:
total:
type: integer
description: The full unfiltered count.
limit:
type: integer
offset:
type: integer
count:
type: integer
description: Records in the returned page.
example:
meta:
total: 4
limit: 50
offset: 0
count: 4
ProjectEnvelope:
type: object
properties:
data:
$ref: '#/components/schemas/Project'
DeletedBase:
type: object
required:
- deleted
properties:
deleted:
type: boolean
const: true
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'An ApiKey presented as a Bearer credential. Keys carry the `frq_api_` prefix and are shown once at creation (only their SHA-256 digest is stored). Isolated from the MCP server''s Personal Access Tokens (`frq_`).
A key belongs to the **company**, not to a person: it is administered from /settings/api_keys by the company owner or anyone granted the `manage_api_keys` permission, who can see and revoke every key the company has. Its capability is its own `allowed_operations` and its reach is the whole company; the person who created it is recorded as a signature and is not whose permissions the call runs with.
So a key stops authenticating (401) for two reasons and no others: revocation and expiry. With the API switched off for the company, a valid key gets 404 instead: the surface is hidden. A key is explicitly unaffected by its creator changing role or leaving.
'