Events

Company calendar events. Numeric ids.

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

The resource

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

idinteger

Numeric id (events have no slug).

urlstring | null

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

namestring
descriptionstring | null
starts_atstring (date-time)
ends_atstring (date-time)
all_dayboolean
meeting_urlstring | null
organizerstring | null

The person who organized the event. A key can edit or delete any event in its company, whoever organized it.

organizer_emailstring | null
attendeesobject[]
Event
{
  "id": 77,
  "url": "https://app.fareeqy.com/events/77",
  "name": "اجتماع مراجعة التصاميم",
  "description": "نراجع نسخة الهيدر ونقرر الاتجاه النهائي.",
  "starts_at": "2026-08-05T13:00:00.000+03:00",
  "ends_at": "2026-08-05T14:00:00.000+03:00",
  "all_day": false,
  "meeting_url": "https://meet.google.com/abc-defg-hij",
  "organizer": "سارة العتيبي",
  "organizer_email": "sara@example.com",
  "attendees": [
    {
      "name": "عبدالله المطيري",
      "email": "abdullah@example.com"
    }
  ]
}

List calendar events

GET/eventsRequires events:read

Every event on the key's company calendar, whoever organized it. Defaults to upcoming only; an explicit from/to window wins over include_past. Bounds and the "upcoming" cutoff resolve in the company time zone.

Query parameters

fromstring

Lower bound on start time (ISO 8601). An explicit window overrides include_past.

tostring

Upper bound on start time (ISO 8601).

include_pastboolean

When no from/to window is given, include past events.

Default: false

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/events" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": [
    {
      "id": 77,
      "url": "https://app.fareeqy.com/events/77",
      "name": "اجتماع مراجعة التصاميم",
      "description": "نراجع نسخة الهيدر ونقرر الاتجاه النهائي.",
      "starts_at": "2026-08-05T13:00:00.000+03:00",
      "ends_at": "2026-08-05T14:00:00.000+03:00",
      "all_day": false,
      "meeting_url": "https://meet.google.com/abc-defg-hij",
      "organizer": "سارة العتيبي",
      "organizer_email": "sara@example.com",
      "attendees": [
        {
          "name": "عبدالله المطيري",
          "email": "abdullah@example.com"
        }
      ]
    }
  ],
  "meta": {
    "total": 4,
    "limit": 50,
    "offset": 0,
    "count": 4
  }
}
Refusals:401403404422429Show 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."
  }
}
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

Create an event

POST/eventsRequires events:write

Routes through the shared Events::Creator, so attendees are notified and the event syncs to a connected Google Calendar. Naive times parse in the company zone.

Request body

namestringrequired
descriptionstring | null
starts_atstringrequired

ISO 8601 (e.g. 2026-07-20T14:00).

ends_atstringrequired

ISO 8601.

all_dayboolean
meeting_urlstring | null
attendee_emailsstring (email)[]

Emails of assignable company users to invite.

Request

cURL
curl -X POST "https://app.fareeqy.com/api/v1/events" \
  -H "Authorization: Bearer $FAREEQY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"اجتماع مراجعة التصاميم","description":"نراجع نسخة الهيدر ونقرر الاتجاه النهائي.","starts_at":"2026-08-05T13:00","ends_at":"2026-08-05T14:00","all_day":false,"meeting_url":"https://meet.google.com/abc-defg-hij","attendee_emails":["abdullah@example.com"]}'

Response

201
{
  "data": {
    "id": 77,
    "url": "https://app.fareeqy.com/events/77",
    "name": "اجتماع مراجعة التصاميم",
    "description": "نراجع نسخة الهيدر ونقرر الاتجاه النهائي.",
    "starts_at": "2026-08-05T13:00:00.000+03:00",
    "ends_at": "2026-08-05T14:00:00.000+03:00",
    "all_day": false,
    "meeting_url": "https://meet.google.com/abc-defg-hij",
    "organizer": "سارة العتيبي",
    "organizer_email": "sara@example.com",
    "attendees": [
      {
        "name": "عبدالله المطيري",
        "email": "abdullah@example.com"
      }
    ]
  }
}
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 an event

GET/events/{id}Requires events:read

Path parameters

idintegerrequired

The event's NUMERIC id (events have no ref and no slug).

Request

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

Response

200
{
  "data": {
    "id": 77,
    "url": "https://app.fareeqy.com/events/77",
    "name": "اجتماع مراجعة التصاميم",
    "description": "نراجع نسخة الهيدر ونقرر الاتجاه النهائي.",
    "starts_at": "2026-08-05T13:00:00.000+03:00",
    "ends_at": "2026-08-05T14:00:00.000+03:00",
    "all_day": false,
    "meeting_url": "https://meet.google.com/abc-defg-hij",
    "organizer": "سارة العتيبي",
    "organizer_email": "sara@example.com",
    "attendees": [
      {
        "name": "عبدالله المطيري",
        "email": "abdullah@example.com"
      }
    ]
  }
}
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 an event

PATCH/events/{id}Requires events:write

Any event in the key's company, whoever organized it. Only passed fields change, EXCEPT attendee_emails, which replaces the whole guest list when present.

Path parameters

idintegerrequired

The event's NUMERIC id (events have no ref and no slug).

Request body

namestring
descriptionstring | null
starts_atstring

ISO 8601.

ends_atstring

ISO 8601.

all_dayboolean
meeting_urlstring | null
attendee_emailsstring (email)[]

When present, REPLACES the entire attendee list.

Request

cURL
curl -X PATCH "https://app.fareeqy.com/api/v1/events/77" \
  -H "Authorization: Bearer $FAREEQY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"","description":null,"starts_at":"","ends_at":"","all_day":false,"meeting_url":null,"attendee_emails":[""]}'

Response

200
{
  "data": {
    "id": 77,
    "url": "https://app.fareeqy.com/events/77",
    "name": "اجتماع مراجعة التصاميم",
    "description": "نراجع نسخة الهيدر ونقرر الاتجاه النهائي.",
    "starts_at": "2026-08-05T13:00:00.000+03:00",
    "ends_at": "2026-08-05T14:00:00.000+03:00",
    "all_day": false,
    "meeting_url": "https://meet.google.com/abc-defg-hij",
    "organizer": "سارة العتيبي",
    "organizer_email": "sara@example.com",
    "attendees": [
      {
        "name": "عبدالله المطيري",
        "email": "abdullah@example.com"
      }
    ]
  }
}
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 an event

DELETE/events/{id}Requires events:destructive

Permanent. Any event in the key's company, whoever organized it. Cascades to personal copies others made of a public event.

Path parameters

idintegerrequired

The event's NUMERIC id (events have no ref and no slug).

Request

cURL
curl -X DELETE "https://app.fareeqy.com/api/v1/events/77" \
  -H "Authorization: Bearer $FAREEQY_API_KEY"

Response

200
{
  "data": {
    "deleted": true,
    "event": {
      "id": 77,
      "name": "اجتماع مراجعة التصاميم"
    }
  }
}
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