For developers

External Integrations with the Fareeqy API

The Fareeqy API is a REST API for reading, creating, and updating projects, task lists, tasks, Majlis discussions, files, folders, calendar events, members, and comments. Each request requires a Bearer key, a scope, and allowed operations. The API is in preview and enabled per company.

Base URL
https://app.fareeqy.com/api/v1
Version
v1.0.0
OpenAPI 3.1
OpenAPI file
Status
Preview, enabled per company

Start in three steps

  1. Mint an API key in your company settings, and pick its scope and the operations it carries.
  2. Check the key works, and see which operations it carries and which company it acts for.
  3. Read your projects, then work inside them: lists, tasks, majlis and files.
Request
curl "https://app.fareeqy.com/api/v1/me" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"
Response
{
  "data": {
    "key": {
      "name": "مزامنة الفوترة",
      "access": "write",
      "operations": [
        "projects:read",
        "tasks:read",
        "tasks:write"
      ]
    },
    "company": {
      "name": "فريق التقنية"
    },
    "created_by": {
      "name": "سارة العتيبي",
      "email": "sara@example.com"
    }
  }
}

created_by is a signature that tells you which key an integration carries. It is not the identity the request runs as.

Access and activation

The API is available on the Advanced, Productive and Enterprise plans. The Free and Pro plans carry no API access at all: a request from them is refused with 403 and plan_upgrade_required, and retrying will never help.

The surface is in preview and switched on per company. While it is off for yours, every authenticated path 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.

To switch the API on for your company, or to ask for higher limits, get in touch and we will enable it on your account.

Authentication

Every request carries its API key in the Authorization header as a Bearer credential. A key is shown once at creation (only its hashed digest is stored), so save yours when you mint it.

cURL
curl "https://app.fareeqy.com/api/v1/me" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

REST keys and MCP tokens are separate credentials; neither authenticates against the other's interface.

Key authority

  • The key belongs to the company, not its creator, and works after they leave.

  • An operation not granted to the key ← 403.

  • The key reaches every company project, including private ones.

  • A record belonging to another company ← 404.

  • created_by names the key creator, not the request identity.

  • Key administration: owner and authorized users; not managers or members.

Scope and operations

Every endpoint declares the single operation it requires, a stable <resource>:<capability> string such as projects:write. A key is gated twice before a request runs.

  • Scope is the ceiling: read or write. A write key also carries read. The scope decides which operations a key may ever hold.

  • The allowed-operations list is the operative gate: the explicit operations this key may call, validated to be a subset of what its scope permits. Empty means nothing, so a key is never accidentally open.

Both must hold: the operation must be in the key's list, and its scope must satisfy the operation's capability. Otherwise the request is refused with 403 and audited as denied.

Deletes are opt-in. destructive operations need the write scope but are deliberately excluded from the default grant: each delete is ticked on a key by hand. See the operation catalog below.

Identifiers

Projects, task lists, tasks and majlis topics are addressed by a readable slug. A slug is unique only within its parent, so URLs are fully nested and every slug resolves through its parent: the same task-list slug can repeat across projects. Never assume a slug is globally unique.

Files, folders, events and comments are addressed by numeric ids. Those ids are global, so every lookup is drilled through the parent project (or, for events, the caller's accessible set): an id from another company resolves to 404, never a leak.

Slugs are Arabic because your project names are. URL-encode them before sending: تطوير-الموقع becomes %D8%AA%D8%B7%D9%88%D9%8A%D8%B1-.... The examples here show the raw text so they stay readable.

Response envelopes

Every response is enveloped, so you never handle a bare array or a root-level field.

  • Single resource: { "data": { ... } }

  • Collection: { "data": [ ... ], "meta": { total, limit, offset, count } }

  • Delete: { "data": { "deleted": true, ... } }

  • Error: { "error": { "code": "...", "message": "..." } }

Collections take limit (default 50, max 100, clamped) and offset (default 0). meta.total is the full unfiltered count; meta.count is the number of records in the returned page.

Rate limits and the daily quota

Two independent layers, and they refuse differently.

Burst. Each key is throttled to 300 requests per minute, and a matching per-IP throttle applies too. Crossing either returns 429.

Daily quota. Every request spends one call from the company's daily allowance. The allowance is per company, not per key, so minting extra keys does not raise it, and it resets at midnight in your company's own time zone.

PlanDaily API calls
FreeNo API access
ProNo API access
Advanced1,000
Productive10,000
EnterpriseCustom, request a quote

A plan with no access is not being rate-limited: its refusal is 403 plan_upgrade_required and carries no Retry-After, because waiting will never help. A plan that has access and has spent today's calls gets 429 rate_limit_exceeded with a Retry-After pointing at your company's next midnight. Treating the two as the same failure sends a well-behaved client into a retry loop that can never succeed.

X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset are set on every served response, not only on refusals, so your client can slow down before it reaches zero. On an uncapped plan the first two read unlimited.

Operation catalog

The table lists every operation that can be granted to a key, grouped by resource. Delete operations are never granted automatically; each one must be enabled individually.

ResourceReadWriteDestructive
accountaccount:readNoneNone
projectsprojects:readprojects:writeprojects:destructive
task_liststask_lists:readtask_lists:writetask_lists:destructive
taskstasks:readtasks:writetasks:destructive
discussionsdiscussions:readdiscussions:writediscussions:destructive
commentsNonecomments:writeNone
filesfiles:readfiles:writefiles:destructive
eventsevents:readevents:writeevents:destructive

Errors

All errors use a shared shape containing a stable machine-readable code and a human-readable message. These errors may be returned by any endpoint.

401Unauthorized

Missing or invalid API key.

401
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid or missing API key."
  }
}

403Forbidden

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

This key may not perform this operation

403
{
  "error": {
    "code": "forbidden",
    "message": "This API key is not permitted to perform this operation."
  }
}

The company's plan carries no API access

403
{
  "error": {
    "code": "plan_upgrade_required",
    "message": "خطة «الاحترافي» لا تشمل الوصول إلى API. رقِّ إلى «المتطور» أو «الانتاجي» لتفعيله. — The الاحترافي plan does not include API access. Upgrade to «المتطور» or «الانتاجي» to enable it."
  }
}

404NotFound

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.

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

422Unprocessable

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

422
{
  "error": {
    "code": "unprocessable_entity",
    "message": "Title can't be blank"
  }
}

409Conflict

A uniqueness/record conflict; retry.

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

429RateLimited

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 sent with it

Retry-After Seconds to wait before retrying. On a spent daily allowance this points at the company's next midnight.

X-RateLimit-Limit The plan's daily API allowance, or unlimited.

X-RateLimit-Remaining Calls left in today's allowance, or unlimited.

X-RateLimit-Reset Unix timestamp of the next reset (the company's next midnight).

Today's daily allowance is spent (application envelope)

429
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "استهلكت رصيد اليوم من طلبات API في خطة «المتطور» (1000 طلب يوميًا). يتجدد الرصيد عند منتصف الليل بتوقيت Asia/Riyadh. — Daily API quota exhausted: the المتطور plan allows 1000 calls per day. It resets at midnight Asia/Riyadh."
  }
}

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

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

All endpoints

The index lists every endpoint. Open an endpoint's group for its parameters, request body, and a response example.

Account

The identity of the calling key, and unified workspace search.

Enable the API and Issue a Key

Request API activation for your company. Once enabled, issue a key from API Keys settings and select its allowed operations.