# BusinessHRM API (v1) - full guide and reference OpenAPI spec: https://app.businesshrm.com/developers/openapi.json Interactive docs: https://app.businesshrm.com/developers Agent rules: https://app.businesshrm.com/developers/agents.md Welcome to the **BusinessHRM REST API**. Everything in your workspace (people, attendance, leave, payroll, projects, tasks, chat, CRM, invoices, tickets and more) is available over one predictable HTTPS + JSON API, the same one the BusinessHRM mobile app uses. | | | |---|---| | **Base URL** | `https://app.businesshrm.com/api/v1` or your own `https://yourcompany.businesshrm.com/api/v1` (identical: the key decides the company) | | **Format** | JSON in and out. Send `Accept: application/json` and, with a body, `Content-Type: application/json` | | **Auth** | `Authorization: Bearer ` | | **Spec** | [OpenAPI 3.1](/developers/openapi.json) · [llms.txt](/developers/llms.txt) · [llms-full.txt](/developers/llms-full.txt) · [For AI agents](/developers/agents.md) | | **AI assistants** | MCP endpoint `https://yourcompany.businesshrm.com/mcp` (see *Connect AI tools (MCP)* at the end) | **How it fits together, end to end:** you create an API key in the app, choosing what it may touch (scopes). Every request carries that key. The server works out the company from the key, checks the key's scopes, then checks the role permissions of the person who owns the key, and only then reads or writes, returning only the rows that person may see. Lists are paged; writes can carry an idempotency key so retries are safe; every response carries rate-limit headers; errors share one JSON shape. To react to changes without polling, register a webhook (signed HTTP POSTs) or, for chat, subscribe to the realtime channel. --- ## 1. Getting started ### Step 1: create an API key 1. Sign in as a company admin and open **Settings → API Keys → Create API Key**. 2. Give it a name that says what uses it ("Payroll export", "Door reader 2"). 3. Choose **Specific permissions** and tick only what the integration needs, or **Full access**. Set an expiry if the integration is temporary. 4. Copy the key. It is shown **once**; store it in your secret manager, never in source code or a mobile app bundle. A key looks like `123|AbCdEf...`. Revoke or rotate it on the same page at any time; a revoked key stops working immediately. ### Step 2: understand what a key can do Two checks apply to every request, and both must pass: 1. **The key's scopes.** A key limited to *Employees* and *Attendance* gets **403** on `/invoice`. A scope also covers its sub-resources (*Contacts* covers `contact-category`). *Full access* lifts this check only. 2. **The owner's role.** A key never does more than the person who created it can do in the app. If that role cannot view employees, `GET /employee` is 403 even for a *Full access* key. Where a permission is *owned*/*added*, lists return only those rows (an employee's key sees only their own payslips). | Scope | Covers | |---|---| | `contact` | Contacts, contact categories, tags, companies | | `lead` | Leads and the pipeline | | `project`, `task`, `timelog` | Projects (incl. project health), tasks (incl. My Work, saved views), time logs | | `invoice`, `estimate`, `expense`, `product`, `currency` | Finance | | `employee`, `attendance`, `leave`, `holiday`, `department`, `designation` | People and time off | | `payroll`, `loan` | Payslips, pay timing, payroll cycles; loans (read-only) | | `ticket`, `chat`, `notice`, `event`, `contract`, `dashboard` | Support, chat, notices, events, contracts, dashboards | Client (contact) accounts get read-only access to their own invoices, estimates, projects, tasks, tickets, events and notices. ### Step 3: make your first request List the first five employees (only the fields you need): ```bash curl -G "https://app.businesshrm.com/api/v1/employee" \ -H "Authorization: Bearer $BUSINESSHRM_API_KEY" \ -H "Accept: application/json" \ --data-urlencode "fields=id,name,email" \ --data-urlencode "limit=5" ``` **JavaScript** (Node 18+ or the browser; never ship a key inside a public web page): ```js const res = await fetch( "https://app.businesshrm.com/api/v1/employee?fields=id,name,email&limit=5", { headers: { Authorization: `Bearer ${process.env.BUSINESSHRM_API_KEY}`, Accept: "application/json" } } ); if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).message}`); const { data, meta } = await res.json(); console.log(data, meta.paging.total); ``` **PHP** (Guzzle): ```php $client = new GuzzleHttp\Client([ 'base_uri' => 'https://app.businesshrm.com/api/v1/', 'headers' => ['Authorization' => 'Bearer ' . getenv('BUSINESSHRM_API_KEY'), 'Accept' => 'application/json'], ]); $res = $client->get('employee', ['query' => ['fields' => 'id,name,email', 'limit' => 5]]); $body = json_decode((string) $res->getBody(), true); print_r($body['data']); ``` **Python** (requests): ```python import os, requests s = requests.Session() s.headers.update({"Authorization": f"Bearer {os.environ['BUSINESSHRM_API_KEY']}", "Accept": "application/json"}) r = s.get("https://app.businesshrm.com/api/v1/employee", params={"fields": "id,name,email", "limit": 5}) r.raise_for_status() print(r.json()["data"]) ``` Create something (a contact) the same way, with a JSON body and an idempotency key so a retry can never create a duplicate: ```bash curl -X POST "https://app.businesshrm.com/api/v1/contact" \ -H "Authorization: Bearer $BUSINESSHRM_API_KEY" \ -H "Content-Type: application/json" -H "Accept: application/json" \ -H "Idempotency-Key: 7d1f2c64-5b8e-4f1e-9a51-2c1d0e9b7a10" \ -d '{"name": "Acme Corporation", "email": "buyer@example.com", "contact_detail": {"company_name": "Acme Corporation"}}' ``` ### Signing in as a person (mobile and desktop apps) Apps used by a person sign in with their own e-mail and password instead of an admin's API key: `POST /auth/login` returns a token for that person, `GET /auth/me` returns who they are, `POST /auth/logout` ends the session. Everything else works exactly as with a key, limited to that person's permissions. Use API keys for server-to-server integrations. **Continue with Google (apps).** `GET /auth/providers` tells an app whether Google sign-in is switched on and which Google web client id to request the ID token for. The app then sends that ID token to `POST /auth/google` and gets the same answer as `POST /auth/login`. Only people who already have an account can sign in this way - nobody is signed up from an app. --- ## 2. Authentication details - Send the key on **every** request in `Authorization: Bearer `. Keys in query strings are not accepted. - A missing, revoked or expired key answers **401**. A key that is valid but not allowed answers **403** with a message naming the missing scope or permission, for example `Your role does not permit "view_employees"`. - The company is taken from the key, never from the host name, so a key for one company can never read another company's data. - Sensitive values are never returned by any endpoint: passwords, tokens, two-factor secrets and codes, API secrets. --- ## 3. Response shape **A single record** comes back as: ```json { "message": "Resource created successfully", "data": { "id": 576 }, "meta": { "time": 0.07 } } ``` **A list** comes back as a `data` array plus paging info in `meta`: ```json { "data": [ { "id": 148, "name": "Osvaldo Conn", "email": "osvaldo@acme.com" } ], "meta": { "paging": { "total": 104, "links": { "next": "https://app.businesshrm.com/api/v1/contact?offset=20" } }, "time": 0.02 } } ``` --- ## 4. Pagination Use `limit` (how many) and `offset` (where to start). `meta.paging.total` is the full count and `meta.paging.links.next` is the ready-to-call next page. ```bash # second page of 20 curl "https://app.businesshrm.com/api/v1/contact?limit=20&offset=20" -H "Authorization: Bearer YOUR_API_KEY" ``` Default page size is 20; the maximum is 1000 (more answers 400). Feed-style endpoints (chat messages, mentions, search) use a cursor instead: pass `before_id` = `meta.next_before_id` for the next older page, or `after_id` for only newer items. --- ## 5. Choosing fields By default each endpoint returns a sensible set of fields. Ask for exactly the ones you want with `fields` (comma-separated) to get smaller, faster responses: ```bash curl "https://app.businesshrm.com/api/v1/contact?fields=id,name,email" -H "Authorization: Bearer YOUR_API_KEY" ``` --- ## 6. Sorting Use `order` with a column and a direction. On lists that join tables (contacts, employees) qualify the column with its table: ```bash curl -G "https://app.businesshrm.com/api/v1/contact" -H "Authorization: Bearer YOUR_API_KEY" \ --data-urlencode "order=users.name asc" ``` Several columns: `order=status asc,id desc`. An unknown or ambiguous column answers **400** naming it. --- ## 7. Filtering & Search Filtering uses one `filters` parameter. The shape is always: ``` filters=(field operator value) ``` Combine conditions with `and` / `or`. **You can copy any example below and just change the values.** | You want… | Use this filter | |---|---| | Status is exactly *active* | `filters=(status eq "active")` | | Name contains *jan* (search) | `filters=(users.name lk "%jan%")` (contacts and employees store the name on the joined `users` table) | | Email is a specific address | `filters=(email eq "buyer@acme.com")` | | Two conditions (both true) | `filters=((status eq "active") and (name lk "%acme%"))` | | Either condition | `filters=((status eq "active") or (status eq "pending"))` | **Operators:** `eq` equals · `ne` not equals · `gt` greater than · `ge` greater-or-equal · `lt` less than · `le` less-or-equal · `lk` like/contains (use `%` as the wildcard). **Full, ready-to-run example** — active employees whose name contains "sam": ```bash curl -G "https://app.businesshrm.com/api/v1/employee" \ -H "Authorization: Bearer YOUR_API_KEY" \ --data-urlencode 'filters=(status eq "active" and users.name lk "%sam%")' ``` > Tip: when testing in a browser/Postman, remember to URL-encode the filter (spaces become `%20`, `"` becomes `%22`). The `curl -G --data-urlencode` form above does this for you. You can only filter on fields a resource marks as filterable — each endpoint lists them. Anything else answers **400** `Applying filter on field "x" is not allowed`. --- ## 8. Errors Every error has the same shape, whatever the endpoint: ```json { "message": "Request could not be validated", "error": { "message": "Request could not be validated", "code": 422, "details": { "email": ["The email field is required."] } } } ``` `error.details` (validation errors per field) appears on 422 validation failures; other errors carry `error.innererror.code`, a stable number for programmatic handling (2001 bad request, 2003 bad field in a query, 2006 not permitted, 3001 not found, 2409/2422/2423 idempotency, 2429/2430 rate limits). Show `message` to people; branch on the HTTP status and `innererror.code`. | Status | Meaning | |---|---| | 400 | Bad request: an unknown field in `fields`/`order`/`filters`, too large a `limit` | | 401 | Missing or invalid API key | | 403 | Authenticated, but not permitted — either your role lacks the permission for this resource, or your key was not granted it | | 404 | Not found, or you may not see it (a private record answers 404, not 403, so ids cannot be probed) | | 409 | The same `Idempotency-Key` is still being processed | | 422 | Validation failed (check `message`) | | 429 | Rate limit reached - see Rate limits below | | 500 | Something went wrong on our side | --- ## 9. Rate limits Your allowance is counted **per app, per resource**. An "app" is the API key (or marketplace app) making the call; a "resource" is the company it is calling on behalf of. Because the budget is the pair, installing on more sub-accounts does **not** divide your allowance - each install gets its own. | Limit | Allowance | Window | |---|---|---| | Burst | 100 API requests | per 10 seconds | | Daily | 200,000 API requests | per day (resets midnight UTC) | ### Tracking your usage Every response carries your current position against both limits. Read these rather than counting requests yourself - they are authoritative. | Header | Meaning | |---|---| | `X-RateLimit-Limit-Daily` | Your daily limit | | `X-RateLimit-Daily-Remaining` | Requests remaining for the day | | `X-RateLimit-Interval-Milliseconds` | The time interval for burst requests | | `X-RateLimit-Max` | The maximum request limit in that interval | | `X-RateLimit-Remaining` | Requests remaining in the current interval | ### When you go over You get `429 Too Many Requests` with a `Retry-After` header giving the number of seconds to wait, and a body that tells the two cases apart: ```json { "message": "Too many requests. Slow down and retry shortly.", "error": { "code": "rate_limit_exceeded", "innererror": { "code": 2430 } } } ``` `innererror.code` is **2430** for the burst limit and **2429** for the daily one. A burst 429 is worth retrying after the wait; a daily 429 means you are done until midnight UTC and is worth alerting a human about. **Pace yourself from the headers.** A client that waits until it is refused wastes the round trip; one that slows down as `X-RateLimit-Remaining` falls never sees a 429 at all. --- ## 10. Idempotency (safe retries) Networks fail mid-request. To retry a **POST** without creating the record twice, send a unique `Idempotency-Key` header (8 to 255 characters: letters, digits, `- _ : .`; a UUID is ideal) and reuse the same key when you retry the same request. ```bash curl -X POST "https://app.businesshrm.com/api/v1/leave/apply" \ -H "Authorization: Bearer $BUSINESSHRM_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: leave-2026-11-03-user-12" \ -d '{"leave_type_id": 1, "leave_date": "2026-11-03", "reason": "Family event"}' ``` | Situation | What you get | |---|---| | First request succeeds (2xx) | The response, which is stored for **24 hours** | | Same key, same request, again | The **stored** response, with header `Idempotent-Replayed: true`; nothing is written again | | Same key, different body or path | **422** `This Idempotency-Key was already used for a different request.` | | Same key while the first is still running | **409**: wait a moment and retry | | First request failed (4xx or 5xx) | Nothing is stored; the retry runs normally | Keys are private to the API key (or signed-in person) and the company. Without the header, behaviour is unchanged. Works on every POST under `/api/v1`. --- ## 11. Webhooks (get told when something changes) Instead of polling, let BusinessHRM call you. 1. In the app, open **Settings → Webhooks** and add a destination: your HTTPS URL (or a Slack / Discord channel). Or do it with the API: `POST /webhook-endpoint` with `name`, `url` and `events` (types from `GET /webhook-events`); the answer contains the signing secret once. `POST /webhook-endpoint/{id}/test` sends a sample, `POST /webhook-endpoint/{id}/rotate-secret` rotates the secret with a 24-hour overlap. 2. Create triggers for that destination, in the app or with the API (`POST /webhook` with `entity`, `event` and `webhooks_setting_id`), for example *Leave / approved*, *Task / completed*, *Invoice / paid*. `GET /webhook-events` lists every entity and event: the same event names Automation triggers use, so a workflow and a webhook can react to the same thing. 3. When the event happens, BusinessHRM sends an HTTP `POST` with the record as JSON. Answer with any **2xx** within 10 seconds; do the slow work after responding. **Delivery rules:** deliveries are at-least-once and may arrive out of order: deduplicate on the event id (`X-BusinessHRM-Event-Id`) and re-read the record with the API when order matters. A failed delivery (timeout, network error, non-2xx) is retried with increasing back-off (honouring `Retry-After`); an endpoint that keeps failing is paused and admins are told. Every attempt is logged: see it in Settings → Webhooks or with `GET /webhook-endpoint/{id}/deliveries`, and resend one with `POST /webhook-delivery/{id}/resend`. ### Verifying signatures Endpoints created with the current API version are signed (endpoints created before keep their old unsigned format until you edit them). A signed delivery carries: | Header | Meaning | |---|---| | `X-BusinessHRM-Signature` | `t=,v1=` (during a secret rotation a second `v1=` signed with the previous secret is added) | | `X-BusinessHRM-Event` / `X-BusinessHRM-Event-Id` | Event type and its unique id (deduplicate on this) | | `X-BusinessHRM-Delivery` | Id of this delivery attempt | | `X-BusinessHRM-Timestamp` | Same time as `t=` | `v1` is `HMAC-SHA256(secret, t + "." + raw_request_body)`, lowercase hex, using the destination's signing secret (`whsec_...`, shown in Settings → Webhooks). Always verify against the **raw** body bytes, before any JSON parsing, compare in constant time, and reject a `t` older than 5 minutes. **Node.js (Express):** ```js import crypto from "node:crypto"; import express from "express"; const app = express(); app.post("/hooks/businesshrm", express.raw({ type: "application/json" }), (req, res) => { const header = req.get("X-BusinessHRM-Signature") || ""; const parts = Object.groupBy(header.split(","), p => p.split("=")[0]); const t = Number((parts.t?.[0] || "").split("=")[1]); const sigs = (parts.v1 || []).map(p => p.split("=")[1]); const expected = crypto.createHmac("sha256", process.env.BHRM_WEBHOOK_SECRET) .update(`${t}.${req.body}`).digest("hex"); const ok = Math.abs(Date.now() / 1000 - t) <= 300 && sigs.some(s => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected))); if (!ok) return res.status(400).send("bad signature"); const event = JSON.parse(req.body); res.sendStatus(200); // answer first, then process queueForProcessing(event); }); ``` **PHP:** ```php $body = file_get_contents('php://input'); $header = $_SERVER['HTTP_X_BUSINESSHRM_SIGNATURE'] ?? ''; $t = 0; $sigs = []; foreach (explode(',', $header) as $part) { [$k, $v] = array_pad(explode('=', trim($part), 2), 2, ''); if ($k === 't') { $t = (int) $v; } elseif ($k === 'v1') { $sigs[] = $v; } } $expected = hash_hmac('sha256', $t . '.' . $body, getenv('BHRM_WEBHOOK_SECRET')); $valid = abs(time() - $t) <= 300 && array_filter($sigs, fn ($s) => hash_equals($expected, $s)); if (!$valid) { http_response_code(400); exit; } http_response_code(200); $event = json_decode($body, true); ``` **Python (Flask):** ```python import hmac, hashlib, os, time from flask import Flask, request, abort app = Flask(__name__) @app.post("/hooks/businesshrm") def hook(): body = request.get_data() # raw bytes parts = [p.split("=", 1) for p in request.headers.get("X-BusinessHRM-Signature", "").split(",") if "=" in p] t = int(next((v for k, v in parts if k == "t"), "0")) sigs = [v for k, v in parts if k == "v1"] expected = hmac.new(os.environ["BHRM_WEBHOOK_SECRET"].encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest() if abs(time.time() - t) > 300 or not any(hmac.compare_digest(expected, s) for s in sigs): abort(400) return "", 200 ``` --- ## 12. Realtime (chat) Chat events are pushed over WebSockets using the **Pusher protocol**, so any Pusher client library works. 1. `GET /chat/realtime` returns the connection settings (`key`, `host`, `port`, `tls`, `path`), your personal `user_channel`, the channel name patterns and the event names. If `enabled` is `false`, poll instead (below). 2. Connect, then subscribe to your `user_channel` (badges and new-message activity everywhere) and to the channel of the conversation on screen. 3. Channels are private. Point the client's auth endpoint at `POST /api/v1/chat/realtime/auth` with your bearer token; it signs only channels you may read. 4. Events carry ids, not message bodies: on `chat.message`, fetch `GET /chat/messages?type=…&conversation_id=…&after_id=`. ```js import Pusher from "pusher-js"; const cfg = (await api("GET", "/chat/realtime")).data; const pusher = new Pusher(cfg.key, { wsHost: cfg.host, wsPort: cfg.port, wssPort: cfg.port, forceTLS: cfg.tls, wsPath: cfg.path, enabledTransports: ["ws", "wss"], cluster: "", channelAuthorization: { endpoint: "https://yourcompany.businesshrm.com/api/v1/chat/realtime/auth", headers: { Authorization: `Bearer ${token}` } }, }); pusher.subscribe(cfg.user_channel).bind("chat.activity", e => refreshBadges(e)); ``` **Polling fallback:** every 10 seconds call `GET /chat/messages?…&after_id=` for the open conversation and `GET /chat/unread` for the badge. Mark a conversation read with `POST /chat/read`; read state is per person. --- ## 13. Conventions at a glance | Topic | Convention | |---|---| | Lists | `data` array + `meta.paging` (`total`, `links.next`); `limit` (default 20, max 1000) and `offset` | | Feeds (chat, mentions, search) | Cursor paging: `before_id` / `after_id` + `limit` (max 100); `meta.next_before_id`, `meta.has_more` | | Fields | `fields=id,name,relation{id,name}`; relations one level deep | | Sorting | `order=column asc|desc` (qualify joined columns, e.g. `users.name asc`) | | Filtering | `filters=(field op value)` with `and` / `or` | | Dates | Input `YYYY-MM-DD` / `YYYY-MM-DD HH:MM:SS`; timestamps returned in ISO 8601 | | Writes | JSON body; create/update return the full record; POST accepts `Idempotency-Key` | | Errors | `{ "message": ..., "error": { "message", "code", "details"?, "innererror": { "code" } } }` | | Limits | `X-RateLimit-*` headers on every response; 429 + `Retry-After` | --- ## 14. Versioning and changes The API is versioned in the path (`/api/v1`). Within v1 we only make **additive** changes: new endpoints, new optional parameters and new response fields. Write clients that ignore fields they do not know. Anything that changes existing behaviour (for example a request that used to succeed now answering 403 or 422 for security reasons) is listed in the **Changelog** below, and features on the preview of the next version are listed under **Unreleased (preview)** before they go live. A breaking change would ship as `/api/v2`, with v1 kept running. --- ## 15. Guides ### Sync employees into another system 1. Page through the directory: `GET /employee?fields=id,name,email,status,employee_detail{employee_id,department_id,designation_id,joining_date}&limit=100&offset=0`, following `meta.paging.links.next` until it is absent. Deactivated people are included; filter with `filters=(status eq "active")` if you only want current staff. 2. Map ids with `GET /department` and `GET /designation`. 3. Store each `id` as the external key. On later runs, upsert by that id. 4. To stay current without full re-syncs, add webhook triggers for *Employee / created* and *Employee / updated* and re-read the record when one arrives. Required scopes: `employee`, `department`, `designation`. ### Push attendance from a device (door reader, kiosk, biometric bridge) Use a key owned by an HR admin with the `attendance` scope. ```bash # clock someone in (one record per person per day; the time is in the company time zone) curl -X POST "https://app.businesshrm.com/api/v1/attendance" \ -H "Authorization: Bearer $BUSINESSHRM_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: reader2-u12-2026-11-03T09:01" \ -d '{"user_id": 12, "clock_in_time": "2026-11-03 09:01:00", "working_from": "office"}' # later, clock them out by updating that record curl -X PUT "https://app.businesshrm.com/api/v1/attendance/987" \ -H "Authorization: Bearer $BUSINESSHRM_API_KEY" -H "Content-Type: application/json" \ -d '{"clock_out_time": "2026-11-03 18:04:00"}' ``` Map the device's badge numbers to `user_id`s once (`GET /employee?fields=id,name,employee_detail{employee_id}`). Use an idempotency key built from device + person + time so a device that re-sends its buffer never doubles a punch. Read back with `GET /attendance?filters=(user_id eq 12)`. ### Create and send invoices 1. Find or create the customer: `POST /contact/upsert` (matches by e-mail). 2. `POST /invoice` with `contact_id`, `issue_date`, `due_date`, `currency_id` and `items` (name, quantity, unit price, taxes). Send an `Idempotency-Key` built from your own order number. 3. Send it: `GET /invoice/send/{id}` e-mails the customer. 4. Track payment with a webhook trigger on *Invoice / paid*, or poll `GET /invoice/{id}?fields=id,status,total,due_amount`. Required scopes: `contact`, `invoice` (+ `product`, `currency` for lookups). ### Book appointments > **Not available yet.** The appointment-booking module is still being built for this preview; its API endpoints will be added here and in the changelog when it is merged. Until then, create calendar events with `POST /event` (name, `start_date_time`, `end_date_time`, attendees), read a person's calendar with `GET /me/calendar?start=…&end=…`, and let invitees reply with `POST /event/{id}/rsvp`. ### Receive webhooks 1. Expose an HTTPS endpoint that reads the **raw** body. 2. Verify the signature (section 11), answer 2xx immediately, then queue the work. 3. Deduplicate on the event id; fetch the latest state from the API before acting on old events. 4. Watch your endpoint's delivery log in **Settings → Webhooks** while testing. --- ## 16. Working with Contact tags Contact tags are always referenced by **name** (never by id). When you send a name that doesn't exist yet, it is **created automatically** — so you never manage tag ids. There are two different ways to set tags, and they behave differently: | What you call | What happens to existing tags | |---|---| | `contact_tags` on **Create / Update / Upsert** | **Replaces the whole list.** Only the tags you send remain; any others on the contact are removed. | | `POST /contact/{id}/tags` | **Adds** the given tags and keeps the existing ones. | | `DELETE /contact/{id}/tags` | **Removes** only the named tags (the tag itself is not deleted, just detached). | **Example:** a contact already has `["A", "B", "C", "D", "E"]`. - Send `"contact_tags": ["VIP", "Newsletter"]` on **update** → the contact ends up with **only** `["VIP", "Newsletter"]` (A–E are removed). - Call **POST** `/contact/{id}/tags` with `{ "tags": ["VIP", "Newsletter"] }` → the contact ends up with `["A","B","C","D","E","VIP","Newsletter"]`. - Call **DELETE** `/contact/{id}/tags` with `{ "tags": ["A"] }` → the contact ends up with `["B","C","D","E"]`. So: use `contact_tags` when you want to set the **complete** tag list; use the add/remove endpoints when you want to change **individual** tags without resending everything. --- ## Webhooks (events pushed to you) Instead of polling, let BusinessHRM call your URL when something happens — 100+ events across contacts, deals, tasks, invoices, payments, employees, attendance, leave, payroll/payslips, tickets, calendar, chat, e-signatures, performance and recruit. Every request is signed (`X-BusinessHRM-Signature`, HMAC-SHA256), retried with exponential backoff and logged with request/response. Set endpoints up in the app under **Webhooks**; the full guide, verification snippets and the event catalogue with sample payloads are at **[/developers/webhooks](/developers/webhooks)**. Each event is also listed in this reference under *Webhooks*. ## 17. Changelog ### Unreleased (preview) These changes are on the preview of the next version and are **not live yet**. Everything listed under *New endpoints* is additive: no existing route, field name or response shape changed. **New endpoints** | Endpoint | Methods | What it is for | |---|---|---| | `auth/providers` | GET | Public: which sign-in methods the server offers (password, Google) and the Google web client id an app should request its ID token for. | | `auth/google` | POST | Public, rate-limited: sign in with a Google ID token; same response as `auth/login`. Existing accounts only. | | `task/my-work` | GET | The caller's open tasks grouped Overdue / Today / This week / Later / No date, plus snoozed tasks, recent updates from others and which actions to offer. | | `task/quick-add/preview` | POST | Parse one line (`#project @person !high tomorrow 3pm`) without creating anything. | | `task/quick-add` | POST | Create a task from one line. Same permission as the task form. | | `task/{id}/complete` | POST | Move a task to its board's finished column (respects `change_status` and project approval). | | `task/{id}/snooze` | POST | Hide a task from your My Work until tomorrow, in 3 days or next week (`clear` to undo). | | `task/bulk-update` | POST | Assign, set due date, priority, labels or board on up to 500 tasks. Needs `edit_tasks` at *all*. | | `task-view`, `task-view/{id}` | GET, POST, DELETE | Your own saved filter sets for the task and project lists. | | `project-health` | GET | Portfolio: every project you can see with health (on track / at risk / off track / done). | | `project/{id}/health` | GET | One project's health and whether you may post a status update. | | `project/{id}/status-updates` | GET, POST | A project's status updates; posting one sets its health for 14 days. | | `payroll/{id}/payslip` | GET | A payslip ready to render: work period vs pay date, pay timing, earnings and deductions as lines, previous-period adjustment lines with their source. | | `payroll/pay-timing` | GET | The company's pay timing and the caller's own (salary groups can override), with this month's pay date. | | `chat/conversations` | GET | Every direct chat, group and channel you can read, with unread counts. | | `chat/messages` | GET, POST | Cursor-paged messages of one conversation (`before_id` / `after_id`); send to a direct chat, group or channel, with mentions. | | `chat/messages/{id}/thread` | GET, POST | A thread's root and replies; reply in a thread. | | `chat/messages/{id}/reactions`, `…/pin`, `…/seen-by` | POST, POST, GET | Toggle a reaction, pin or unpin, see who has read it. | | `chat/pins`, `chat/read`, `chat/unread`, `chat/mentions`, `chat/search` | GET/POST | Pinned messages, per-person read state, unread total, messages that mention you, message search. | | `chat/realtime`, `chat/realtime/auth` | GET, POST | Live updates: connection settings, channel names, Pusher-protocol channel authorisation. | | `chat/messages/{id}` | PUT, DELETE | Edit your own message; delete yours (admins: any they can see). | | `chat/messages/{id}/reactions` | GET, DELETE | Who reacted with what; remove your reaction. | | `chat/unread-counts` | GET | Unread messages per conversation. | | `userchat/messages/{id}`, `chat-channel/{id}/messages` | GET | Now also take `before_id` / `after_id` cursors (20 per page). Without them nothing changes. | | `event/{id}/rsvp` | POST | Reply to an event invitation: `going`, `maybe`, `declined`. Attendee lists and `me/calendar` carry `rsvp` / `rsvp_at`. | | `me/calendar-feed`, `me/calendar-feed/reset` | GET, POST | Your private calendar subscription link (.ics) and resetting it. | | `webhook-events` | GET | The catalogue of webhook (and Automation) events. | | `performance/reviews`, `performance/reviews/{id}`, `…/respond`, `…/sign-off` | GET, POST | Reviews to do / mine / my team; one review as the caller may see it; fill in or submit your form; sign off your own review. | | `performance/review-cycles`, `performance/review-templates` | GET | Cycles with progress and templates with questions (HR). | | `performance/feedback` | GET, POST | Feedback received / given / team / praise wall; give feedback. | | `performance/pips`, `performance/pips/{id}`, `…/acknowledge` | GET, POST | Improvement plans; the employee acknowledges theirs. | | `performance/goal-tree`, `performance/goals/{id}` | GET | Goals as a tree with `parent_id`, `weight`, progress and key-result `progress_source` / `source_project_id`. | | `hr-documents`, `hr-documents/letters/{id}` (+ `/pdf`), `hr-documents/policy-compliance` | GET | The HR documents hub: issued letters and offers (filter by type, stage, status), a letter and its PDF, policy compliance. | | `my-documents`, `my-documents/letters/{id}` (+ `/pdf`, `/acknowledge`) | GET, POST | An employee's own letters, offers and policies, with what still needs action. | | `reports`, `reports/{key}`, `reports/{key}/export/{format}`, `reports/preferences` | GET, POST | The Reports hub: list, run (KPIs, series, table), export CSV/Excel/PDF, favourite or pin. | | `task/everything`, `task/{id}/inline-edit` | GET, POST | The Everything list (grouped, filtered, timeline) and one-field edits. | | `task/{id}/relations`, `task/relations/search`, `task/{id}/watch`, `task/{id}/watchers` | GET, POST, DELETE | Waiting-on / blocking / related links and watchers. | | `task-template`, `task/{id}/save-as-template`, `task-template/{id}/use` | GET, POST, DELETE | Task templates. | | `task-board/{id}/statuses` | GET, POST | A board's statuses with colours and status groups. | | `webhook-endpoint` (+ `/{id}`, `/rotate-secret`, `/test`, `/deliveries`), `webhook-delivery/{id}` (+ `/resend`) | GET, POST, PUT, DELETE | Manage webhook destinations, their signing secret, test events and the delivery log. | | `branding` | GET | The company's colours, font and logos, to theme an app. | | `pdf-templates`, `pdf-templates/resolve` | GET | Which PDF layout each document type (and contact) uses. | | `ticket-forms`, `ticket-forms/{id}` | GET | The public support forms and their fields (submitting is a public web form). | **Invoices: tips.** Invoices carry `allow_tip` (writable), `tip_amount` and `tip_selected` (read-only: set by the payer and by payments). **New webhook and Automation events** (one shared list, `GET /webhook-events`): `performance_review.submitted`, `performance_review.signed_off`, `review_cycle.launched`, `review_cycle.closed`, `goal.at_risk`, `feedback.given` (never for private notes), `pip.created`, `hr_document.issued`, `hr_document.signed`, `hr_document.pack_sent`. **Fixed: webhook triggers were not validated.** `POST /webhook` accepted any `entity` / `event` and any `webhooks_setting_id`, including another company's destination. Both are now checked (422 with the reason). **New: idempotency keys.** Every POST accepts an `Idempotency-Key` header; a retry with the same key returns the stored response instead of writing twice (section 10). **New API key scopes:** `payroll` and `chat` can now be granted to a key with specific permissions. **New: access control for API keys and AI assistants.** When you create an API key or connect an AI assistant you now choose, per area, *No access* / *Read* / *Read and change*, an optional read-only switch, which sensitive data categories may be shared (off by default: salary, bank, national ID, leave reasons; their fields are removed from answers) and an optional IP allowlist. Admins set the company ceiling, per-role limits, AI on/off, confirmation for destructive AI actions and an AI rate limit in *Settings → AI & integrations*, which also lists connected assistants (edit/revoke) and the AI activity log (view/export). New endpoint `GET /ai/access` shows what the calling key or connection may do. Keys created before this change keep full module access, still limited by their scopes and the owner's role. **MCP server:** tools now carry `title` and annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint: false`), tools a connection may not use are hidden, destructive tools need a confirmation (code or elicitation), results are returned as `structuredContent` and wrapped as untrusted data, browser Origins are checked, the generic request tool only reaches documented paths, and company API keys now work as a bearer header on the hosted endpoint (they were refused before). Budgets in the project health endpoints are only returned to callers who may see project budgets (`view_project_budget` *all*, or admins). None of the new endpoints is open to client accounts. **New fields on existing responses** `GET payroll` and `GET payroll/{id}` now also carry `slip_type` (`regular` / `settlement`), `pay_timing`, `pay_date`, `counted_to`, `payroll_run_id` and `released_at`. Add them to `fields=` to request them. **Behaviour changes** - **`PUT payroll/{id}` only changes `status`, `paid_on` and `salary_payment_method_id`.** Any other field (for example `net_salary`) now answers **422** naming the refused fields. Previously such fields were written straight to the payslip, bypassing the rule that amounts are recalculated from their lines and that paid or locked payslips are read-only. Edit amounts in the app. - **Payroll endpoints now honour API key scopes.** `payroll`, `payroll-cycle` and `payroll/cycle-data` skipped the key-scope check, so a key without the *payroll* scope could read payslips. Such a key now gets **403**. Full-access keys and sign-in tokens are unaffected. - **Payslips of a payroll run stay hidden from employees until the run is released.** `GET payroll` leaves them out and `GET payroll/{id}` answers 403 for an employee until then. Admins and `view_payroll` *all* see them as before. Payslips made outside a payroll run are unchanged. - **One-day leave is stored as `single`.** `POST leave/apply` for one date used to save `duration: "full day"`; it now saves `single`, matching the web form. Treat `single`, `full day` and anything other than `half day` or `multiple` as a full day. Sending `"duration": "full day"` is still accepted. - **Sign-in details are never serialised.** A few module endpoints returned the signed-in account record, including two-factor secrets and pending codes. Those fields are no longer included anywhere. - **A bad column in `order`, `fields` or `filters` answers 400 instead of 500**, with a message naming the column (for example `order=-id` on `/contact`, where `id` is ambiguous; use `order=users.id desc`). - **The default company logo URL changed** for companies without a logo (`logo_url` now points to the BusinessHRM icon). **Web-app security fixes in this round (no API change)** Permission checks were tightened in the web app for contacts, tickets, payslip edits and status changes, loans files and approvals, finance (offline payments, credit notes, expense and product categories, recurring expenses), policies (employees never see drafts), recruitment settings, events, asset types, performance and biometric settings, and ticket-from-message. These are browser screens; no REST API endpoint started answering 403 because of them. ### 23 September 2026 **Rate limiting is now enforced.** Until this date the API had no request budget of any kind: the throttle configured in the application never reached these routes, because they are registered by a router that attaches its own middleware. If you have been calling without pacing, read section 9 and the `X-RateLimit-*` headers before you next deploy. **New endpoints** | Endpoint | Methods | What it is for | |---|---|---| | `contact-tag` | GET, POST | List the workspace's contact tags, or create one. Tags were always writable by name, but nothing listed them, so a picker had no options. | | `contact-company` | GET, POST | Companies a contact can belong to. `contact_detail.contact_company_id` already saved; there was no way to list or create one. | | `custom-field?entity=` | GET | The custom fields defined for an entity: `id`, `key`, `name`, `label`, `type`, `required`, `visible`, `values`. `custom_fields_data` was always accepted on write, with no way to learn a field existed. Entities: contact, employee, project, task, lead, deal, invoice, estimate, expense, product, event, ticket, timelog, contract. | | `project-status` | GET | The statuses a project may hold in this company. `projects.status` stores the NAME and the values are per company. | | `project-label` | GET | Labels a project can carry. Attaching them via `labels:[{id}]` already worked. | **Fixed: writes that were silently dropped** - **Tasks.** `billable`, `estimate_hours`, `estimate_minutes`, `dependent_task_id` and `repeat` / `repeat_count` / `repeat_type` / `repeat_cycles` were accepted, answered 200, and stored nothing. They now save. - **Contacts.** Any save carrying `custom_fields_data` failed outright with a 500. It now stores. **Fixed: requests that were wrongly refused** - **Project create** validated `status` against a hardcoded list that spelled cancelled with two Ls where the data has one, omitted `finished`, and refused any workspace that had renamed or added a status. It now validates against the company's own statuses. - **A task could never have its due date removed.** `without_duedate` is now honoured on create and update, matching the flag projects already had. - **`abort_403()` answered 500.** Every permission refusal on the API reported itself as a server fault. 4xx aborts now answer with their own status. **Fixed: fields that could be written but not read** `task_category_id` and `projects.category_id` were both hidden on read, so a client could set a category and never show which one was set. **New: filtering** Tasks may now be filtered on `priority`, `due_date` and `status`. **New: password reset actually works** `auth/forgot-password` and `auth/reset-password` have been routed since this module shipped and pointed at methods that did not exist, so both answered 500. They are implemented now. `forgot-password` always answers 200 whether or not the address exists, so it cannot be used to discover who has an account. `reset-password` revokes every token the identity holds on success. **New: chat mentions are stored** `POST userchat` accepts `mention_user_ids`, and the message exposes `mentionUser{id,name}` for reading back. `mention_users.user_chat_id` had existed since the table was created with nothing ever writing to it. Ids are filtered to people the sender may actually message. Note that this is storage only: no notification is delivered for a chat mention yet. --- Pick a resource on the left to see every endpoint, every field, and a copy-paste example you can run immediately. Use the **Test Request** button to call the API right from this page. --- ## Connect AI tools (MCP) Connect AI assistants such as Claude, ChatGPT, Codex, Cursor, VS Code or Windsurf to your workspace with the **Model Context Protocol**. The assistant gets tools generated from this API reference (one per endpoint, ~400 tools), named after their operation (`get_employee`, `post_leave_apply`, `get_chat_messages`, `get_ai_access`…). ``` https://.businesshrm.com/mcp ``` Use your own workspace address. **Transport:** Streamable HTTP (the current MCP standard; the old HTTP+SSE transport is not offered). **Auth:** one-click sign-in (OAuth 2.1 with PKCE and dynamic client registration) or an API key in the `Authorization` header. ### What a connection can do, and who decides Every tool call goes through the same checks as the REST API, in this order: 1. **The company policy** (admins: *Settings → AI & integrations*): AI access on or off, the most any connection or key may get per area, limits per role, which sensitive data may ever be shared, confirmation for destructive actions, an AI call rate limit, and your plan's limit. 2. **The connection's own grant**, chosen on the sign-in screen (or when creating the API key): each area (People, Attendance, Leave, Payroll, Projects, Tasks, CRM, Finance, Support, Chat, Workspace) set to *No access*, *Read* or *Read and change*; an optional *Read-only* switch; sensitive data categories, **off unless ticked** (salary and pay amounts, bank details, national ID / passport / visa numbers, leave reasons); an optional IP allowlist; expiry. Edit or revoke it any time in *Settings → AI & integrations* (connections) or *Settings → API Keys* (keys). 3. **The person's own role.** An assistant never does more than the person who connected it can do in the app, and lists only show the rows that person may see. Tools the connection may not use are not offered to the assistant at all; anything else is still refused by the API. Passwords, two-factor secrets and API secrets are never returned by any tool. With a sensitive category off, its fields are removed from every answer and its screens (payslips, bank details, immigration) are refused. **Destructive actions need a human.** Deletes, bulk updates and actions that e-mail people (send invoice or estimate, reminders, invitations) are marked `destructiveHint`. When the company requires confirmation (the default), the first call changes nothing and returns a summary plus a one-time code; the assistant must show it to the person and repeat the call with that code. Clients that support MCP *elicitation* show a yes/no prompt instead. Read-only tools are marked `readOnlyHint`. **Audit.** Every AI tool call is logged: when, who, which assistant or key, which tool and endpoint, result. Arguments and answers are never stored. Admins view and export the log in *Settings → AI & integrations*. ### Claude (claude.ai, Claude Desktop, mobile) 1. *Settings → Connectors → Add custom connector*. 2. Name it "BusinessHRM" and paste `https://.businesshrm.com/mcp`. 3. Click **Connect**, sign in to BusinessHRM, choose your workspace, choose what Claude may do, then **Allow**. Team and Enterprise plans: an owner adds the connector once for the organisation; each person then connects with their own BusinessHRM login. ### Claude Code One-click sign-in: ```bash claude mcp add --transport http businesshrm https://.businesshrm.com/mcp # then in Claude Code run /mcp, pick businesshrm and sign in ``` Or with an API key (CI, scripts): ```bash claude mcp add --transport http businesshrm https://.businesshrm.com/mcp \ --header "Authorization: Bearer $BUSINESSHRM_API_KEY" ``` Share it with a project through `.mcp.json` (the key stays in each person's environment): ```json { "mcpServers": { "businesshrm": { "type": "http", "url": "https://.businesshrm.com/mcp", "headers": { "Authorization": "Bearer ${BUSINESSHRM_API_KEY}" } } } } ``` ### ChatGPT 1. *Settings → Connectors (Apps & Connectors) → Advanced settings* → turn on **Developer mode** (your plan and workspace admin must allow custom connectors). 2. **Create** a connector: name "BusinessHRM", URL `https://.businesshrm.com/mcp`, authentication **OAuth**. 3. Sign in to BusinessHRM and choose what ChatGPT may do. Review write actions before ChatGPT runs them. ### Codex (CLI and IDE extension) `~/.codex/config.toml`: ```toml [mcp_servers.businesshrm] url = "https://.businesshrm.com/mcp" bearer_token_env_var = "BUSINESSHRM_API_KEY" ``` Or sign in instead of using a key: leave out `bearer_token_env_var` and run `codex mcp login businesshrm`. ### Cursor `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (everywhere): ```json { "mcpServers": { "businesshrm": { "url": "https://.businesshrm.com/mcp", "headers": { "Authorization": "Bearer ${env:BUSINESSHRM_API_KEY}" } } } } ``` Leave out `headers` to use one-click sign-in. ### VS Code (GitHub Copilot agent mode) `.vscode/mcp.json`: ```json { "inputs": [{ "type": "promptString", "id": "bhrm-key", "description": "BusinessHRM API key", "password": true }], "servers": { "businesshrm": { "type": "http", "url": "https://.businesshrm.com/mcp", "headers": { "Authorization": "Bearer ${input:bhrm-key}" } } } } ``` ### Windsurf `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "businesshrm": { "serverUrl": "https://.businesshrm.com/mcp", "headers": { "Authorization": "Bearer YOUR_API_KEY" } } } } ``` ### Other clients Any MCP client that speaks Streamable HTTP works: point it at the URL above and either let it sign in (OAuth discovery starts from the `401` + `WWW-Authenticate` answer, then `/.well-known/oauth-protected-resource`) or send `Authorization: Bearer `. Clients that only run local (stdio) servers can bridge with `npx mcp-remote https://.businesshrm.com/mcp --header "Authorization: Bearer "`. ### Security and privacy - **Your data goes only to the assistant you connect.** BusinessHRM sends tool results to that client and nowhere else; what the assistant's provider does with it is governed by your agreement with them. If you handle health or other regulated data (for example under HIPAA), connect only assistants covered by a suitable agreement (such as a BAA), keep the relevant sensitive categories off, and use read-only grants. - Tokens are bound to your workspace's MCP address (audience-checked), short-lived (1 hour, refreshed by the client) and revocable at once. API keys are never accepted in URLs. - There is no tool that fetches arbitrary web addresses: the generic `businesshrm_request` tool only reaches documented API paths. - Browser requests from other websites are refused (Origin check), and tool results are wrapped as untrusted data so the assistant does not mistake workspace text for instructions. - Each connection has its own AI call rate limit (company setting) on top of the API limits. ### Troubleshooting | Symptom | Fix | |---|---| | `401` / sign-in loops | The key or connection was revoked, expired or its sign-in was declined. Reconnect, or create a new key. | | A tool is missing | The connection was not granted that area, or it is read-only. Check with the `get_ai_access` tool, then edit the connection in *Settings → AI & integrations*. | | `403 … no access to Payroll & loans` / `… not allowed to see salary` | Grant the area and tick the sensitive category, if the company policy allows it. | | `403 Your role does not permit "…"` | The person's role lacks that permission; an admin changes it in *Settings → Roles & permissions*. | | `403 AI access is turned off` | An admin turned off AI assistants for the company. | | "CONFIRMATION REQUIRED" | Expected for deletes and bulk or e-mailing actions: confirm with the person, then repeat the call with the code. | | `429` | Too many AI calls per minute for this connection; wait the `Retry-After` seconds. | | Tools changed after an update | Restart or reconnect the client; the tool list is generated from this reference. | --- # Endpoint reference Paths are relative to /api/v1. `*` = required body field. Permission rules are in each description. ## Sales (CRM) ### Contacts People and companies you do business with. Supports create, read, update, delete, list and **upsert** (create-or-update by email). #### POST /contact/upsert Upsert a Contact Creates a contact, or updates the existing one if a record with the same email already exists. Use this when you don't want to track ids yourself. Body: name*, email*, password, mobile, salutation, gender, locale, country_id, country_phonecode, login, email_notifications, contact_tags, sendMail, contact_detail Responses: 200, 401, 403, 422 #### POST /contact/{id}/tags Add tags to a Contact Adds one or more tags (by name) to a contact **without** removing existing tags. Tags are matched by name; any that do not exist yet are created automatically. Parameters: id (path, required) Body: tags* Responses: 200, 401 #### DELETE /contact/{id}/tags Remove tags from a Contact Removes one or more tags (by name) from a contact. The tags themselves are not deleted — they are just detached from this contact. Parameters: id (path, required) Body: tags* Responses: 200, 401 #### GET /contact List Contacts Returns a paginated list of Contacts in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /contact Create a Contact Creates a new contact. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: name*, email*, password, mobile, salutation, gender, locale, country_id, country_phonecode, login, email_notifications, contact_tags, sendMail, contact_detail Responses: 200, 401, 403, 422 #### GET /contact/{contact} Get a Contact Fetches a single contact by its id, including its detail fields. Parameters: contact (path, required) Responses: 200, 401, 403, 422 #### PUT /contact/{contact} Update a Contact Updates an existing contact by its id. Send only the fields you want to change; everything else stays as-is. Parameters: contact (path, required) Body: name, email, password, mobile, salutation, gender, locale, country_id, country_phonecode, login, email_notifications, contact_tags, sendMail, contact_detail Responses: 200, 401, 403, 422 #### DELETE /contact/{contact} Delete a Contact Permanently deletes a contact by its id. This cannot be undone. Parameters: contact (path, required) Responses: 200, 401, 403, 422 ### Contact Categories Manage contact categories. #### GET /contact-category List Contact Categories Returns a paginated list of Contact Categories in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /contact-category Create a Contact Category Creates a new contact category. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: category_name* Responses: 200, 401, 403, 422 #### GET /contact-category/{contact_category} Get a Contact Category Fetches a single contact category by its id, including its detail fields. Parameters: contact_category (path, required) Responses: 200, 401, 403, 422 #### PUT /contact-category/{contact_category} Update a Contact Category Updates an existing contact category by its id. Send only the fields you want to change; everything else stays as-is. Parameters: contact_category (path, required) Body: category_name Responses: 200, 401, 403, 422 #### DELETE /contact-category/{contact_category} Delete a Contact Category Permanently deletes a contact category by its id. This cannot be undone. Parameters: contact_category (path, required) Responses: 200, 401, 403, 422 ### Contact Sub-categories Manage contact sub-categories. #### GET /contact-sub-category List Contact Sub-categories Returns a paginated list of Contact Sub-categories in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /contact-sub-category Create a Contact Sub-category Creates a new contact sub-category. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: category_name* Responses: 200, 401, 403, 422 #### GET /contact-sub-category/{contact_sub_category} Get a Contact Sub-category Fetches a single contact sub-category by its id, including its detail fields. Parameters: contact_sub_category (path, required) Responses: 200, 401, 403, 422 #### PUT /contact-sub-category/{contact_sub_category} Update a Contact Sub-category Updates an existing contact sub-category by its id. Send only the fields you want to change; everything else stays as-is. Parameters: contact_sub_category (path, required) Body: category_name Responses: 200, 401, 403, 422 #### DELETE /contact-sub-category/{contact_sub_category} Delete a Contact Sub-category Permanently deletes a contact sub-category by its id. This cannot be undone. Parameters: contact_sub_category (path, required) Responses: 200, 401, 403, 422 ### Deals CRM deals in your sales pipeline. Supports create, read, update, delete and list. A new deal defaults to your first pipeline/stage unless you pass lead_pipeline_id and pipeline_stage_id. #### GET /deal List Deals Returns a paginated list of Deals in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /deal Create a Deal Creates a new deal. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: name, column_priority, lead_pipeline_id, pipeline_stage_id, lead_id, contact_id, contact_type, close_date, agent_id, category_id, next_follow_up, value, note, currency_id, deal_watcher, create_contact Responses: 200, 401, 403, 422 #### GET /deal/{deal} Get a Deal Fetches a single deal by its id, including its detail fields. Parameters: deal (path, required) Responses: 200, 401, 403, 422 #### PUT /deal/{deal} Update a Deal Updates an existing deal by its id. Send only the fields you want to change; everything else stays as-is. Parameters: deal (path, required) Body: name, column_priority, lead_pipeline_id, pipeline_stage_id, lead_id, contact_id, contact_type, close_date, agent_id, category_id, next_follow_up, value, note, currency_id, deal_watcher, create_contact Responses: 200, 401, 403, 422 #### DELETE /deal/{deal} Delete a Deal Permanently deletes a deal by its id. This cannot be undone. Parameters: deal (path, required) Responses: 200, 401, 403, 422 ## Work ### Projects Client and internal projects. #### POST /project/{project_id}/members Members (Project) Performs the "Members" action on a project. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/me Me (Project) Performs the "Me" action on a project. Responses: 200, 401, 403, 422 #### GET /project List Projects Returns a paginated list of Projects in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /project Create a Project Creates a new project. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: project_name*, project_summary, start_date, deadline, status, contact_id, category_id, currency_id, project_budget, hours_allocated, team_id Responses: 200, 401, 403, 422 #### GET /project/{project} Get a Project Fetches a single project by its id, including its detail fields. Parameters: project (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project} Update a Project Updates an existing project by its id. Send only the fields you want to change; everything else stays as-is. Parameters: project (path, required) Body: project_name, project_summary, start_date, deadline, status, contact_id, category_id, currency_id, project_budget, hours_allocated, team_id Responses: 200, 401, 403, 422 #### DELETE /project/{project} Delete a Project Permanently deletes a project by its id. This cannot be undone. Parameters: project (path, required) Responses: 200, 401, 403, 422 ### Project Health On track / at risk / off track for every project you can see, one project's health, and the status updates that set it. #### GET /project-health Portfolio: every project with its health Every project the caller may see (the same rule as the project pickers) with health `on_track` / `at_risk` / `off_track` / `done`, sorted worst first then by deadline. Health is automatic (deadline vs progress, overdue tasks, budget and hours used) unless a status update was posted in the last 14 days (`set_by_status_update: true`). `?scope=active` (default: not finished, not cancelled) or `all`; `?health=` filters to one value. `meta.counts` counts each health. **Permission:** Projects module and `view_projects` not *none*. `budget` is only included with `view_project_budget` *all* (or admin). Not available to client accounts. Parameters: scope (query), health (query) Responses: 200, 403 #### GET /project/{project_id}/health One project's health Same fields as the portfolio row plus `can_post_update`. A project you cannot open answers 404. `budget` only with `view_project_budget` *all* (or admin). Parameters: project_id (path, required) Responses: 200, 403, 404 #### GET /project/{project_id}/status-updates A project's status updates Newest first. Paged with `limit` (1-100, default 20) and `offset`. Parameters: project_id (path, required), limit (query), offset (query) Responses: 200, 404 #### POST /project/{project_id}/status-updates Post a status update Sets the project's health for the next 14 days. **Permission:** the same people who may edit the project (`edit_projects` *all*, *added*, *owned*/*both* as a member) or its project admin. Parameters: project_id (path, required) Body: status*, note Responses: 200, 403, 422 ### Project Categories Manage project categories. #### GET /project-category List Project Categories Returns a paginated list of Project Categories in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /project-category Create a Project Category Creates a new project category. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: category_name* Responses: 200, 401, 403, 422 #### GET /project-category/{project_category} Get a Project Category Fetches a single project category by its id, including its detail fields. Parameters: project_category (path, required) Responses: 200, 401, 403, 422 #### PUT /project-category/{project_category} Update a Project Category Updates an existing project category by its id. Send only the fields you want to change; everything else stays as-is. Parameters: project_category (path, required) Body: category_name Responses: 200, 401, 403, 422 #### DELETE /project-category/{project_category} Delete a Project Category Permanently deletes a project category by its id. This cannot be undone. Parameters: project_category (path, required) Responses: 200, 401, 403, 422 ### Tasks Work items, optionally attached to a project. #### GET /task/me Me (Task) Performs the "Me" action on a task. Responses: 200, 401, 403, 422 #### GET /task/remind/{id} Remind (Task) Performs the "Remind" action on a task. Parameters: id (path, required) Responses: 200, 401, 403, 422 #### GET /task List Tasks Returns a paginated list of Tasks in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /task Create a Task Creates a new task. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: heading*, description, priority*, status*, due_date*, start_date, task_users*, project, category, is_private Responses: 200, 401, 403, 422 #### GET /task/{task} Get a Task Fetches a single task by its id, including its detail fields. Parameters: task (path, required) Responses: 200, 401, 403, 422 #### PUT /task/{task} Update a Task Updates an existing task by its id. Send only the fields you want to change; everything else stays as-is. Parameters: task (path, required) Body: heading, description, priority, status, due_date, start_date, task_users, project, category, is_private Responses: 200, 401, 403, 422 #### DELETE /task/{task} Delete a Task Permanently deletes a task by its id. This cannot be undone. Parameters: task (path, required) Responses: 200, 401, 403, 422 #### GET /task/everything Everything list All tasks the caller may see, grouped and paged (100 per page; `meta`-style fields `hasMore`, `page`, and on page 1 `groups` with counts and `total`). `view=timeline` returns dated tasks (up to 500) plus `links` (waiting-on dependencies). Filters take comma-separated ids: `project`, `category`, `milestone`, `board`, `status` (column ids), `label`, `assignee`; `priority` (high,medium,low); `q` text. `project=none` / `category=none` for tasks without one. Parameters: group (query), view (query), sort (query), dir (query), page (query), q (query), project (query), assignee (query), priority (query) Responses: 200, 401 #### GET /task/relations/search Find a task to link Up to 15 visible tasks matching the text. Parameters: q (query), exclude (query) Responses: 200, 401 #### POST /task/{task_id}/inline-edit Edit one field of a task Same as editing a cell in the Everything list. `field`: heading, priority, due_date, start_date, dates, estimate, assignees, milestone; `value` as the field needs (dates YYYY-MM-DD in the company format the web uses). **Permission:** edit rights on the task. Parameters: task_id (path, required) Body: field*, value Responses: 200, 403 #### GET /task/{task_id}/relations Relationships and watchers `waitingOn` (blocked by), `blocking`, `related`, `openBlockers`, `watchers`, whether you are `watching`, and whether you can edit. Linked tasks you may not see are left out. Parameters: task_id (path, required) Responses: 200, 403 #### POST /task/{task_id}/relations Add a relationship `type`: waiting_on (this task waits on the other), blocking (the other waits on this), related. Loops are refused. **Permission:** edit rights on the task; the other task must be visible to you. Parameters: task_id (path, required) Body: type*, other_task_id* Responses: 200, 422 #### DELETE /task/{task_id}/relations/{dependency_id} Remove a relationship Use the `dep` id from the relationships list. Parameters: task_id (path, required), dependency_id (path, required) Responses: 200, 401 #### POST /task/{task_id}/watch Watch or stop watching Toggles your own watch on a task you can see. Parameters: task_id (path, required) Responses: 200, 401 #### POST /task/{task_id}/watchers Add or remove a watcher Only active employees who can see the task can be added. **Permission:** edit rights on the task. Parameters: task_id (path, required) Body: user_id*, remove Responses: 200, 401 #### POST /task/{task_id}/save-as-template Save a task as a template **Permission:** add_tasks (all or added). Parameters: task_id (path, required) Body: name* Responses: 200, 401 #### GET /task-template Task templates **Permission:** add_tasks (all or added). Responses: 200, 401 #### POST /task-template/{template_id}/use Create a task from a template Optional `project_id` and `board`. Employees are assigned to themselves. Parameters: template_id (path, required) Body: project_id, board Responses: 200, 401 #### DELETE /task-template/{template_id} Delete a task template Your own, or any with add_tasks at all. Parameters: template_id (path, required) Responses: 200, 401 ### My Work A person's task inbox and the quick actions on it: My Work groups, natural-language quick add (with a preview that writes nothing), complete, snooze and bulk update. Each endpoint states its permission rule; they match the web app. #### GET /task/my-work My Work: my open tasks, grouped The caller's open tasks (assigned to them, not finished, not waiting for approval), grouped like the My Work page: `overdue`, `today`, `week` (until Sunday), `later`, `no_date`, plus `snoozed`. Groups use the company time zone. `updates` lists what other people did on these tasks in the last 14 days. `abilities` tells the client which actions to offer. **Permission:** Tasks module and `view_tasks` not *none*. At *added* only tasks you created are included. Not available to client accounts. Responses: 200, 401, 403 #### POST /task/quick-add/preview Quick add: preview (no write) Parses one line the way quick add will, without creating anything. Syntax: `#project` (or `#"two words"`), `@person` (or `@me`), `!high` / `!medium` / `!low`, and a date such as `today`, `tomorrow`, `fri`, `next week`, `3 Nov`, `3/11` (day/month order follows the company date format) with an optional time (`3pm`, `15:00`). People and projects are matched only among those the caller may pick. Anything not understood stays in the title and is reported in `warnings`. **Permission:** `view_tasks` not *none*. Body: text* Responses: 200, 403, 422 #### POST /task/quick-add Quick add: create a task from one line Parses `text` (see the preview endpoint) and creates the task on the given board's *Incomplete* column (else its first column), or the company default board. With no `@person` an employee is assigned to themselves. Returns the new task in the My Work shape plus what was parsed. **Permission:** `add_tasks` *all* or *added*, or being the project admin of the parsed project (same as the task form). A project that tracks progress by total time answers 422 when its time is used up. Body: text*, task_board_id Responses: 200, 403, 422 #### POST /task/bulk-update Bulk update tasks One change applied to many tasks, as on the task list. `action` and its value: | action | value | |---|---| | `assign` | `user_ids[]` (employees of the company), optional `replace: true` to replace assignees instead of adding | | `due_date` | `due_date` (Y-m-d); start dates later than it are pulled back | | `priority` | `priority`: high, medium, low | | `labels` | `label_ids[]` (added, existing labels kept) | | `board` | `task_board_id` (tasks move to that board's Incomplete column) | Up to 500 `task_ids`; ids from another company are ignored. **Permission:** `edit_tasks` at level *all*. Body: task_ids*, action*, user_ids, replace, due_date, priority, label_ids, task_board_id Responses: 200, 403, 422 #### POST /task/{task_id}/complete Complete a task Moves the task to its board's finished column (`completed`, else a Done/Shipped/... column, else the last column) and updates project progress, like ticking the circle on My Work. Clears any snooze. **Permission:** `change_status` *all*, *added* (tasks you created), *owned* (tasks assigned to you) or *both*, or project admin. A project that needs admin approval answers 422 for everyone except admins and the project admin. A task you cannot see answers 404. Parameters: task_id (path, required) Responses: 200, 403, 404, 422 #### POST /task/{task_id}/snooze Snooze a task in My Work Hides the task from the caller's My Work groups until a date (it shows under `snoozed`). Personal: other assignees are not affected. `until`: `tomorrow`, `3days`, `nextweek` (next Monday) or `clear`. **Permission:** only an assignee of the task; anyone else gets 404. Parameters: task_id (path, required) Body: until* Responses: 200, 404, 422 ### Saved Views Per-person saved filter sets for the task and project lists. You only ever see and change your own. #### GET /task-view List my saved views The caller's own saved filter sets for a list page. `?page=tasks` (default) or `projects`. `filters` is whatever the client saved: keys and values are opaque to the server, and the list endpoints still apply the caller's normal permissions when a view is used. **Permission:** `view_tasks` or `view_projects` not *none*. Not available to client accounts. Parameters: page (query) Responses: 200, 403 #### POST /task-view Save a view Creates a view, or replaces the filters of your view with the same `name` on the same `page`. `filters`: up to 40 keys, each a string or a list of strings. `is_default: true` makes it your default for that page (the previous default is cleared). Body: name*, page*, filters*, is_default Responses: 200, 422 #### DELETE /task-view/{view_id} Delete one of my saved views Only your own views can be deleted; any other id answers 404. Parameters: view_id (path, required) Responses: 200, 404 ### Task Categories Manage task categories. #### GET /task-category List Task Categories Returns a paginated list of Task Categories in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /task-category Create a Task Category Creates a new task category. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: category_name* Responses: 200, 401, 403, 422 #### GET /task-category/{task_category} Get a Task Category Fetches a single task category by its id, including its detail fields. Parameters: task_category (path, required) Responses: 200, 401, 403, 422 #### PUT /task-category/{task_category} Update a Task Category Updates an existing task category by its id. Send only the fields you want to change; everything else stays as-is. Parameters: task_category (path, required) Body: category_name Responses: 200, 401, 403, 422 #### DELETE /task-category/{task_category} Delete a Task Category Permanently deletes a task category by its id. This cannot be undone. Parameters: task_category (path, required) Responses: 200, 401, 403, 422 ### Task Boards Task boards — the parent of taskboard columns. Create, rename, recolour and delete boards, and set the board-level **AI agent instructions** an assistant follows for every task on it. A new board is created with starter columns (To Do, In Progress, Done) just like one added in the app. Managing boards needs the caller's `add_status` / `edit_status` / `delete_status` permission at level *all*; reading needs only the Tasks module. #### GET /task-board/{board_id}/statuses A board's statuses Columns with colour, status group (not_started, active, done, closed…), task count and whether they are locked (built-in). **Permission:** add_status at all. Parameters: board_id (path, required) Responses: 200, 401 #### POST /task-board/{board_id}/statuses Save a board's statuses Replaces the list (order = array order). Keep `id` for existing statuses; locked ones cannot be removed. **Permission:** add_status at all. Parameters: board_id (path, required) Body: statuses* Responses: 200, 422 #### GET /task-board List Task Boards Returns a paginated list of Task Boards in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /task-board Create a Task Board Creates a new task board. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: name, slug, priority, label_color, ai_instructions, default Responses: 200, 401, 403, 422 #### GET /task-board/{task_board} Get a Task Board Fetches a single task board by its id, including its detail fields. Parameters: task_board (path, required) Responses: 200, 401, 403, 422 #### PUT /task-board/{task_board} Update a Task Board Updates an existing task board by its id. Send only the fields you want to change; everything else stays as-is. Parameters: task_board (path, required) Body: name, slug, priority, label_color, ai_instructions, default Responses: 200, 401, 403, 422 #### DELETE /task-board/{task_board} Delete a Task Board Permanently deletes a task board by its id. This cannot be undone. Parameters: task_board (path, required) Responses: 200, 401, 403, 422 ### Task Board Columns The columns (statuses/stages) of a task board. Each column can carry its own **AI agent instructions** — what an assistant should do with a task while it sits in that column. Pass `task_board_id` to place the column on a specific board; omit it and the company default board is used. Slug and priority are filled in automatically when omitted. Managing columns needs `add_status` / `edit_status` / `delete_status` at level *all*. #### GET /taskboard-columns List Task Board Columns Returns a paginated list of Task Board Columns in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /taskboard-columns Create a Task Board Column Creates a new task board column. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: column_name*, slug, label_color*, status_group, ai_instructions, priority*, task_board_id Responses: 200, 401, 403, 422 #### GET /taskboard-columns/{taskboard_column} Get a Task Board Column Fetches a single task board column by its id, including its detail fields. Parameters: taskboard_column (path, required) Responses: 200, 401, 403, 422 #### PUT /taskboard-columns/{taskboard_column} Update a Task Board Column Updates an existing task board column by its id. Send only the fields you want to change; everything else stays as-is. Parameters: taskboard_column (path, required) Body: column_name, slug, label_color, status_group, ai_instructions, priority, task_board_id Responses: 200, 401, 403, 422 #### DELETE /taskboard-columns/{taskboard_column} Delete a Task Board Column Permanently deletes a task board column by its id. This cannot be undone. Parameters: taskboard_column (path, required) Responses: 200, 401, 403, 422 ### Sub-tasks Manage sub-tasks. #### GET /task/{task_id}/subtask List Sub-tasks Returns a paginated list of Sub-tasks in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: task_id (path, required), limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /task/{task_id}/subtask Create a Sub-task Creates a new sub-task. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Parameters: task_id (path, required) Body: task_id*, title*, due_date, start_date, status, assigned_to, description Responses: 200, 401, 403, 422 #### GET /task/{task_id}/subtask/{subtask} Get a Sub-task Fetches a single sub-task by its id, including its detail fields. Parameters: task_id (path, required), subtask (path, required) Responses: 200, 401, 403, 422 #### PUT /task/{task_id}/subtask/{subtask} Update a Sub-task Updates an existing sub-task by its id. Send only the fields you want to change; everything else stays as-is. Parameters: task_id (path, required), subtask (path, required) Body: task_id, title, due_date, start_date, status, assigned_to, description Responses: 200, 401, 403, 422 #### DELETE /task/{task_id}/subtask/{subtask} Delete a Sub-task Permanently deletes a sub-task by its id. This cannot be undone. Parameters: task_id (path, required), subtask (path, required) Responses: 200, 401, 403, 422 ### Task Comments Manage task comments. #### GET /task/{task_id}/comment List Task Comments Returns a paginated list of Task Comments in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: task_id (path, required), limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /task/{task_id}/comment Create a Task Comment Creates a new task comment. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Parameters: task_id (path, required) Body: comment*, user_id*, task_id* Responses: 200, 401, 403, 422 #### PUT /task/{task_id}/comment/{comment} Update a Task Comment Updates an existing task comment by its id. Send only the fields you want to change; everything else stays as-is. Parameters: task_id (path, required), comment (path, required) Body: comment, user_id, task_id Responses: 200, 401, 403, 422 #### DELETE /task/{task_id}/comment/{comment} Delete a Task Comment Permanently deletes a task comment by its id. This cannot be undone. Parameters: task_id (path, required), comment (path, required) Responses: 200, 401, 403, 422 ### Task Notes Manage task notes. #### GET /task/{task_id}/note List Task Notes Returns a paginated list of Task Notes in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: task_id (path, required), limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /task/{task_id}/note Create a Task Note Creates a new task note. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Parameters: task_id (path, required) Body: task_id*, user_id, note Responses: 200, 401, 403, 422 #### PUT /task/{task_id}/note/{note} Update a Task Note Updates an existing task note by its id. Send only the fields you want to change; everything else stays as-is. Parameters: task_id (path, required), note (path, required) Body: task_id, user_id, note Responses: 200, 401, 403, 422 #### DELETE /task/{task_id}/note/{note} Delete a Task Note Permanently deletes a task note by its id. This cannot be undone. Parameters: task_id (path, required), note (path, required) Responses: 200, 401, 403, 422 ### Files Endpoints for files. #### GET /file/{name} Get a File Fetches a single file by its id, including its detail fields. Parameters: name (path, required) Responses: 200, 401, 403, 422 #### POST /file Create a File Creates a new file. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: user_id*, task_id*, filename*, description, google_url, hashname, size, dropbox_link, external_link, external_link_name Responses: 200, 401, 403, 422 #### GET /task/{task_id}/file List Files Returns a paginated list of Files in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: task_id (path, required), limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /task/{task_id}/file Create a File Creates a new file. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Parameters: task_id (path, required) Body: user_id*, task_id*, filename*, description, google_url, hashname, size, dropbox_link, external_link, external_link_name Responses: 200, 401, 403, 422 #### PUT /task/{task_id}/file/{file} Update a File Updates an existing file by its id. Send only the fields you want to change; everything else stays as-is. Parameters: task_id (path, required), file (path, required) Body: user_id, task_id, filename, description, google_url, hashname, size, dropbox_link, external_link, external_link_name Responses: 200, 401, 403, 422 #### DELETE /task/{task_id}/file/{file} Delete a File Permanently deletes a file by its id. This cannot be undone. Parameters: task_id (path, required), file (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/member/{id} Destroy Member (File) Performs the "Destroy Member" action on a file. Parameters: project_id (path, required), id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/members Members (File) Performs the "Members" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/members-add Store Member (File) Performs the "Store Member" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/members/{member_id} Update Member (File) Performs the "Update Member" action on a file. Parameters: project_id (path, required), member_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/files List Files Returns a paginated list of Files in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: project_id (path, required), limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/files Create a File Creates a new file. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Parameters: project_id (path, required) Body: Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/files/{file_id} Delete a File Permanently deletes a file by its id. This cannot be undone. Parameters: project_id (path, required), file_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/notes Notes (File) Performs the "Notes" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/notes Store Note (File) Performs the "Store Note" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/notes/{note_id} Update Note (File) Performs the "Update Note" action on a file. Parameters: project_id (path, required), note_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/notes/{note_id} Destroy Note (File) Performs the "Destroy Note" action on a file. Parameters: project_id (path, required), note_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/discussions Discussions (File) Performs the "Discussions" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/discussions Store Discussion (File) Performs the "Store Discussion" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/discussion-categories Discussion Categories (File) Performs the "Discussion Categories" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/discussions/{discussion_id} Update Discussion (File) Performs the "Update Discussion" action on a file. Parameters: project_id (path, required), discussion_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/discussions/{discussion_id} Destroy Discussion (File) Performs the "Destroy Discussion" action on a file. Parameters: project_id (path, required), discussion_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/discussions/{discussion_id}/replies Discussion Replies (File) Performs the "Discussion Replies" action on a file. Parameters: project_id (path, required), discussion_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/discussions/{discussion_id}/replies Store Discussion Reply (File) Performs the "Store Discussion Reply" action on a file. Parameters: project_id (path, required), discussion_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/discussions/{discussion_id}/replies/{reply_id} Update Discussion Reply (File) Performs the "Update Discussion Reply" action on a file. Parameters: project_id (path, required), discussion_id (path, required), reply_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/discussions/{discussion_id}/replies/{reply_id} Destroy Discussion Reply (File) Performs the "Destroy Discussion Reply" action on a file. Parameters: project_id (path, required), discussion_id (path, required), reply_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/milestones Milestones (File) Performs the "Milestones" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/milestones Store Milestone (File) Performs the "Store Milestone" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/milestones/{milestone_id} Update Milestone (File) Performs the "Update Milestone" action on a file. Parameters: project_id (path, required), milestone_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/milestones/{milestone_id} Destroy Milestone (File) Performs the "Destroy Milestone" action on a file. Parameters: project_id (path, required), milestone_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/timelogs Timelogs (File) Performs the "Timelogs" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/expenses Expenses (File) Performs the "Expenses" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/invoices Invoices (File) Performs the "Invoices" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/estimates Estimates (File) Performs the "Estimates" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/payments Payments (File) Performs the "Payments" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/tickets Tickets (File) Performs the "Tickets" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/rating Rating (File) Performs the "Rating" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/activity Activity (File) Performs the "Activity" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/credentials Credentials (File) Performs the "Credentials" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/credentials Store Credential (File) Performs the "Store Credential" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/credential-categories Credential Categories (File) Performs the "Credential Categories" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/credentials/{credential_id}/reveal Reveal Credential (File) Performs the "Reveal Credential" action on a file. Parameters: project_id (path, required), credential_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/credentials/{credential_id} Update Credential (File) Performs the "Update Credential" action on a file. Parameters: project_id (path, required), credential_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/credentials/{credential_id} Destroy Credential (File) Performs the "Destroy Credential" action on a file. Parameters: project_id (path, required), credential_id (path, required) Responses: 200, 401, 403, 422 ### Timesheets Manage timesheets. #### GET /task/{task_id}/timelog List Timesheets Returns a paginated list of Timesheets in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: task_id (path, required), limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /timelog/me Me (Timesheet) Performs the "Me" action on a timesheet. Responses: 200, 401, 403, 422 #### GET /timelog List Timesheets Returns a paginated list of Timesheets in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /timelog Create a Timesheet Creates a new timesheet. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: project_id, task_id, user_id*, start_time*, end_time, memo, total_hours, total_minutes, edited_by_user, hourly_rate*, earnings*, approved, approved_by, invoice_id, total_break_minutes, weekly_timesheet_id, rejected, rejected_by, rejected_at, reject_reason Responses: 200, 401, 403, 422 #### PUT /timelog/{timelog} Update a Timesheet Updates an existing timesheet by its id. Send only the fields you want to change; everything else stays as-is. Parameters: timelog (path, required) Body: project_id, task_id, user_id, start_time, end_time, memo, total_hours, total_minutes, edited_by_user, hourly_rate, earnings, approved, approved_by, invoice_id, total_break_minutes, weekly_timesheet_id, rejected, rejected_by, rejected_at, reject_reason Responses: 200, 401, 403, 422 #### DELETE /timelog/{timelog} Delete a Timesheet Permanently deletes a timesheet by its id. This cannot be undone. Parameters: timelog (path, required) Responses: 200, 401, 403, 422 ### Contracts Manage contracts. #### GET /contract List Contracts Returns a paginated list of Contracts in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /contract Create a Contract Creates a new contract. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: contract_number, original_contract_number, contact_id*, project_id, subject*, amount*, original_amount*, contract_type_id, start_date*, original_start_date*, end_date, original_end_date, description, contract_name, alternate_address, contract_note, cell, office, city, state, country, postal_code, contract_detail, currency_id, event_id, company_sign, sign_date, sign_by Responses: 200, 401, 403, 422 #### GET /contract/{contract} Get a Contract Fetches a single contract by its id, including its detail fields. Parameters: contract (path, required) Responses: 200, 401, 403, 422 #### PUT /contract/{contract} Update a Contract Updates an existing contract by its id. Send only the fields you want to change; everything else stays as-is. Parameters: contract (path, required) Body: contract_number, original_contract_number, contact_id, project_id, subject, amount, original_amount, contract_type_id, start_date, original_start_date, end_date, original_end_date, description, contract_name, alternate_address, contract_note, cell, office, city, state, country, postal_code, contract_detail, currency_id, event_id, company_sign, sign_date, sign_by Responses: 200, 401, 403, 422 #### DELETE /contract/{contract} Delete a Contract Permanently deletes a contract by its id. This cannot be undone. Parameters: contract (path, required) Responses: 200, 401, 403, 422 ### Contract Types Manage contract types. #### GET /contract-type List Contract Types Returns a paginated list of Contract Types in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /contract-type Create a Contract Type Creates a new contract type. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: name* Responses: 200, 401, 403, 422 #### GET /contract-type/{contract_type} Get a Contract Type Fetches a single contract type by its id, including its detail fields. Parameters: contract_type (path, required) Responses: 200, 401, 403, 422 #### PUT /contract-type/{contract_type} Update a Contract Type Updates an existing contract type by its id. Send only the fields you want to change; everything else stays as-is. Parameters: contract_type (path, required) Body: name Responses: 200, 401, 403, 422 #### DELETE /contract-type/{contract_type} Delete a Contract Type Permanently deletes a contract type by its id. This cannot be undone. Parameters: contract_type (path, required) Responses: 200, 401, 403, 422 ## Finance ### Invoices Bills issued to contacts. #### GET /invoice/send/{id} Send Invoice (Invoice) Performs the "Send Invoice" action on a invoice. Parameters: id (path, required) Responses: 200, 401, 403, 422 #### GET /invoice/payment-reminder/{id} Remind For Payment (Invoice) Performs the "Remind For Payment" action on a invoice. Parameters: id (path, required) Responses: 200, 401, 403, 422 #### GET /invoice List Invoices Returns a paginated list of Invoices in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /invoice Create a Invoice Creates a new invoice. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: invoice_number*, contact_id*, project_id, issue_date*, due_date*, currency_id*, sub_total*, total*, discount, discount_type, recurring, note Responses: 200, 401, 403, 422 #### GET /invoice/{invoice} Get a Invoice Fetches a single invoice by its id, including its detail fields. Parameters: invoice (path, required) Responses: 200, 401, 403, 422 #### PUT /invoice/{invoice} Update a Invoice Updates an existing invoice by its id. Send only the fields you want to change; everything else stays as-is. Parameters: invoice (path, required) Body: invoice_number, contact_id, project_id, issue_date, due_date, currency_id, sub_total, total, discount, discount_type, recurring, note Responses: 200, 401, 403, 422 #### DELETE /invoice/{invoice} Delete a Invoice Permanently deletes a invoice by its id. This cannot be undone. Parameters: invoice (path, required) Responses: 200, 401, 403, 422 ### Estimates Quotes/proposals issued to contacts. #### GET /estimate/send/{id} Send Estimate (Estimate) Performs the "Send Estimate" action on a estimate. Parameters: id (path, required) Responses: 200, 401, 403, 422 #### GET /estimate List Estimates Returns a paginated list of Estimates in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /estimate Create a Estimate Creates a new estimate. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: contact_id*, valid_till*, currency_id*, sub_total*, total*, discount, discount_type Responses: 200, 401, 403, 422 #### GET /estimate/{estimate} Get a Estimate Fetches a single estimate by its id, including its detail fields. Parameters: estimate (path, required) Responses: 200, 401, 403, 422 #### PUT /estimate/{estimate} Update a Estimate Updates an existing estimate by its id. Send only the fields you want to change; everything else stays as-is. Parameters: estimate (path, required) Body: contact_id, valid_till, currency_id, sub_total, total, discount, discount_type Responses: 200, 401, 403, 422 #### DELETE /estimate/{estimate} Delete a Estimate Permanently deletes a estimate by its id. This cannot be undone. Parameters: estimate (path, required) Responses: 200, 401, 403, 422 ### Expenses Money your company spends. #### POST /expense/{expense_id}/bill Upload Bill (Expense) Performs the "Upload Bill" action on a expense. Parameters: expense_id (path, required) Responses: 200, 401, 403, 422 #### GET /expense List Expenses Returns a paginated list of Expenses in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /expense Create a Expense Creates a new expense. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: item_name*, purchase_date*, purchase_from, price, currency_id*, default_currency_id, exchange_rate, project_id, bill, user_id, status, can_claim, category_id, expenses_recurring_id, created_by, description, approver_id, bank_account_id Responses: 200, 401, 403, 422 #### GET /expense/{expense} Get a Expense Fetches a single expense by its id, including its detail fields. Parameters: expense (path, required) Responses: 200, 401, 403, 422 #### PUT /expense/{expense} Update a Expense Updates an existing expense by its id. Send only the fields you want to change; everything else stays as-is. Parameters: expense (path, required) Body: item_name, purchase_date, purchase_from, price, currency_id, default_currency_id, exchange_rate, project_id, bill, user_id, status, can_claim, category_id, expenses_recurring_id, created_by, description, approver_id, bank_account_id Responses: 200, 401, 403, 422 #### DELETE /expense/{expense} Delete a Expense Permanently deletes a expense by its id. This cannot be undone. Parameters: expense (path, required) Responses: 200, 401, 403, 422 ### Products Products and services you sell. #### GET /product List Products Returns a paginated list of Products in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /product Create a Product Creates a new product. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: name*, price*, description*, purchase_allow, taxes, unit_id, hsn_sac_code Responses: 200, 401, 403, 422 #### GET /product/{product} Get a Product Fetches a single product by its id, including its detail fields. Parameters: product (path, required) Responses: 200, 401, 403, 422 #### PUT /product/{product} Update a Product Updates an existing product by its id. Send only the fields you want to change; everything else stays as-is. Parameters: product (path, required) Body: name, price, description, purchase_allow, taxes, unit_id, hsn_sac_code Responses: 200, 401, 403, 422 #### DELETE /product/{product} Delete a Product Permanently deletes a product by its id. This cannot be undone. Parameters: product (path, required) Responses: 200, 401, 403, 422 ### Taxes Manage taxes. #### GET /tax List Taxes Returns a paginated list of Taxes in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 ### Currencies Manage currencies. #### GET /currency List Currencies Returns a paginated list of Currencies in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /currency Create a Currency Creates a new currency. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: currency_name*, currency_symbol, currency_code*, exchange_rate, is_cryptocurrency, usd_price, no_of_decimal, thousand_separator, decimal_separator, currency_position Responses: 200, 401, 403, 422 #### GET /currency/{currency} Get a Currency Fetches a single currency by its id, including its detail fields. Parameters: currency (path, required) Responses: 200, 401, 403, 422 #### PUT /currency/{currency} Update a Currency Updates an existing currency by its id. Send only the fields you want to change; everything else stays as-is. Parameters: currency (path, required) Body: currency_name, currency_symbol, currency_code, exchange_rate, is_cryptocurrency, usd_price, no_of_decimal, thousand_separator, decimal_separator, currency_position Responses: 200, 401, 403, 422 #### DELETE /currency/{currency} Delete a Currency Permanently deletes a currency by its id. This cannot be undone. Parameters: currency (path, required) Responses: 200, 401, 403, 422 ## HR ### Employees Your team members. #### GET /employee/last-employee-id Last Employee I D (Employee) Performs the "Last Employee I D" action on a employee. Responses: 200, 401, 403, 422 #### GET /employee/me Me (Employee) Performs the "Me" action on a employee. Responses: 200, 401, 403, 422 #### GET /employee List Employees Returns a paginated list of Employees in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /employee Create a Employee Creates a new employee. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: name*, email*, password, salutation, gender, mobile, country_id, country_phonecode, locale, login, rtl, dark_theme, employee_detail, additional_departments Responses: 200, 401, 403, 422 #### GET /employee/{employee} Get a Employee Fetches a single employee by its id, including its detail fields. Parameters: employee (path, required) Responses: 200, 401, 403, 422 #### PUT /employee/{employee} Update a Employee Updates an existing employee by its id. Send only the fields you want to change; everything else stays as-is. Parameters: employee (path, required) Body: name, email, password, salutation, gender, mobile, country_id, country_phonecode, locale, login, rtl, dark_theme, employee_detail, additional_departments Responses: 200, 401, 403, 422 #### DELETE /employee/{employee} Delete a Employee Permanently deletes a employee by its id. This cannot be undone. Parameters: employee (path, required) Responses: 200, 401, 403, 422 ### Departments Manage departments. #### GET /department List Departments Returns a paginated list of Departments in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /department Create a Department Creates a new department. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: team_name*, discord_webhook Responses: 200, 401, 403, 422 #### GET /department/{department} Get a Department Fetches a single department by its id, including its detail fields. Parameters: department (path, required) Responses: 200, 401, 403, 422 #### PUT /department/{department} Update a Department Updates an existing department by its id. Send only the fields you want to change; everything else stays as-is. Parameters: department (path, required) Body: team_name, discord_webhook Responses: 200, 401, 403, 422 #### DELETE /department/{department} Delete a Department Permanently deletes a department by its id. This cannot be undone. Parameters: department (path, required) Responses: 200, 401, 403, 422 ### Designations Manage designations. #### GET /designation List Designations Returns a paginated list of Designations in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /designation Create a Designation Creates a new designation. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: name*, parent_id Responses: 200, 401, 403, 422 #### GET /designation/{designation} Get a Designation Fetches a single designation by its id, including its detail fields. Parameters: designation (path, required) Responses: 200, 401, 403, 422 #### PUT /designation/{designation} Update a Designation Updates an existing designation by its id. Send only the fields you want to change; everything else stays as-is. Parameters: designation (path, required) Body: name, parent_id Responses: 200, 401, 403, 422 #### DELETE /designation/{designation} Delete a Designation Permanently deletes a designation by its id. This cannot be undone. Parameters: designation (path, required) Responses: 200, 401, 403, 422 ### Attendance Clock-in / clock-out records. #### GET /attendance/setting Attendance Setting (Attendance) Performs the "Attendance Setting" action on a attendance. Responses: 200, 401, 403, 422 #### GET /attendance/today Today (Attendance) Performs the "Today" action on a attendance. Responses: 200, 401, 403, 422 #### GET /attendance/monthly-summary Monthly Summary (Attendance) Performs the "Monthly Summary" action on a attendance. Responses: 200, 401, 403, 422 #### POST /attendance/clock-in Clock In (Attendance) Performs the "Clock In" action on a attendance. Responses: 200, 401, 403, 422 #### POST /attendance/clock-out/{attendance} Clock Out (Attendance) Performs the "Clock Out" action on a attendance. Parameters: attendance (path, required) Responses: 200, 401, 403, 422 #### GET /attendance List Attendance Returns a paginated list of Attendance in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /attendance Create a Attendance Creates a new attendance. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: user_id*, clock_in_time*, clock_out_time, working_from, late, half_day Responses: 200, 401, 403, 422 #### GET /attendance/{attendance} Get a Attendance Fetches a single attendance by its id, including its detail fields. Parameters: attendance (path, required) Responses: 200, 401, 403, 422 #### PUT /attendance/{attendance} Update a Attendance Updates an existing attendance by its id. Send only the fields you want to change; everything else stays as-is. Parameters: attendance (path, required) Body: user_id, clock_in_time, clock_out_time, working_from, late, half_day Responses: 200, 401, 403, 422 #### DELETE /attendance/{attendance} Delete a Attendance Permanently deletes a attendance by its id. This cannot be undone. Parameters: attendance (path, required) Responses: 200, 401, 403, 422 ### Leaves Time-off requests. #### GET /leave/by-date List leaves on a specific date Returns every leave visible to the caller whose `leave_date` matches the given date. The `date` query parameter is **required**; omitting it returns HTTP 422. Parameters: date (query, required) Responses: 200, 422 #### GET /leave/balance Balance (Leaf) Performs the "Balance" action on a leaf. Responses: 200, 401, 403, 422 #### POST /leave/leave-multiple Multi Dates Leave Apply (Leaf) Performs the "Multi Dates Leave Apply" action on a leaf. Responses: 200, 401, 403, 422 #### POST /leave/apply Apply (Leaf) Performs the "Apply" action on a leaf. Responses: 200, 401, 403, 422 #### GET /leave List Leaves Returns a paginated list of Leaves in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /leave Create a Leaf Creates a new leaf. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: user*, type*, leave_date*, duration*, reason*, status* Responses: 200, 401, 403, 422 #### GET /leave/{leave} Get a Leaf Fetches a single leaf by its id, including its detail fields. Parameters: leave (path, required) Responses: 200, 401, 403, 422 #### PUT /leave/{leave} Update a Leaf Updates an existing leaf by its id. Send only the fields you want to change; everything else stays as-is. Parameters: leave (path, required) Body: user, type, leave_date, duration, reason, status Responses: 200, 401, 403, 422 #### DELETE /leave/{leave} Delete a Leaf Permanently deletes a leaf by its id. This cannot be undone. Parameters: leave (path, required) Responses: 200, 401, 403, 422 ### Leave Types Manage leave types. #### GET /leave-type List Leave Types Returns a paginated list of Leave Types in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /leave-type Create a Leave Type Creates a new leave type. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: type_name*, color*, no_of_leaves, paid, monthly_limit, effective_after, effective_type, unused_leave, encashed*, allowed_probation*, allowed_notice*, gender, marital_status, department, designation, role, leavetype, over_utilization, jurisdiction_id, policy_key, policy_rule_from, policy_applied_at, policy_summary, carry_forward_cap, accumulation_cap, accrual_method, accrual_hours_ratio, accrual_day_hours, pay_tiers, service_increase, hide_on_payslip Responses: 200, 401, 403, 422 #### GET /leave-type/{leave_type} Get a Leave Type Fetches a single leave type by its id, including its detail fields. Parameters: leave_type (path, required) Responses: 200, 401, 403, 422 #### PUT /leave-type/{leave_type} Update a Leave Type Updates an existing leave type by its id. Send only the fields you want to change; everything else stays as-is. Parameters: leave_type (path, required) Body: type_name, color, no_of_leaves, paid, monthly_limit, effective_after, effective_type, unused_leave, encashed, allowed_probation, allowed_notice, gender, marital_status, department, designation, role, leavetype, over_utilization, jurisdiction_id, policy_key, policy_rule_from, policy_applied_at, policy_summary, carry_forward_cap, accumulation_cap, accrual_method, accrual_hours_ratio, accrual_day_hours, pay_tiers, service_increase, hide_on_payslip Responses: 200, 401, 403, 422 #### DELETE /leave-type/{leave_type} Delete a Leave Type Permanently deletes a leave type by its id. This cannot be undone. Parameters: leave_type (path, required) Responses: 200, 401, 403, 422 ### Holidays Manage holidays. #### GET /holiday List Holidays Returns a paginated list of Holidays in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /holiday Create a Holiday Creates a new holiday. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: date*, occassion Responses: 200, 401, 403, 422 #### GET /holiday/{holiday} Get a Holiday Fetches a single holiday by its id, including its detail fields. Parameters: holiday (path, required) Responses: 200, 401, 403, 422 #### PUT /holiday/{holiday} Update a Holiday Updates an existing holiday by its id. Send only the fields you want to change; everything else stays as-is. Parameters: holiday (path, required) Body: date, occassion Responses: 200, 401, 403, 422 #### DELETE /holiday/{holiday} Delete a Holiday Permanently deletes a holiday by its id. This cannot be undone. Parameters: holiday (path, required) Responses: 200, 401, 403, 422 ### Payroll Employee salary/payroll records. Supports create, read, update, delete and list. #### GET /payroll List Payroll Returns a paginated list of Payroll in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /payroll Create a Payroll Creates a new payroll. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: currency_id, user_id*, salary_group_id, basic_salary, net_salary, month*, year*, paid_on, status, salary_json, extra_json, expense_claims, pay_days*, salary_payment_method_id, tds*, monthly_salary*, gross_salary*, total_deductions*, salary_from, salary_to, payroll_cycle_id, fixed_allowance, expenses_created, expense_id, additional_earning_json, slip_type, pay_timing, pay_date, counted_to, counted_json, payroll_run_id, released_at, status_notes, statutory_json, employer_cost Responses: 200, 401, 403, 422 #### GET /payroll/{payroll} Get a Payroll Fetches a single payroll by its id, including its detail fields. Parameters: payroll (path, required) Responses: 200, 401, 403, 422 #### PUT /payroll/{payroll} Change a payslip's payment status Changes the payment status of one payslip, as the web's *Change status* action does. Only `status`, `paid_on` and `salary_payment_method_id` can be sent; any other field answers 422 naming it (amounts are recalculated from their lines in the app, and paid or locked payslips are read-only there). **Permission:** `edit_payroll` *all*, *owned* (your own slip), *added* (slips you made) or *both*. Parameters: payroll (path, required) Body: status*, paid_on, salary_payment_method_id Responses: 200, 403, 422 #### DELETE /payroll/{payroll} Delete a Payroll Permanently deletes a payroll by its id. This cannot be undone. Parameters: payroll (path, required) Responses: 200, 401, 403, 422 #### GET /payroll-cycle Get Cycle (Payroll) Performs the "Get Cycle" action on a payroll. Responses: 200, 401, 403, 422 #### GET /payroll/cycle-data/{payrollCycleId}/{year} Get Cycle Data (Payroll) Performs the "Get Cycle Data" action on a payroll. Parameters: payrollCycleId (path, required), year (path, required) Responses: 200, 401, 403, 422 ### Payslips & Pay Timing A payslip ready to render (work period vs pay date, earnings and deductions as lines, previous-period adjustment lines) and the company's pay timing. #### GET /payroll/pay-timing Pay timing (company and mine) How the company pays (`company`) and how the caller is paid (`mine`; a salary group can override timing and pay day), with the pay date for the current monthly period. Timings: `arrears` (pay after the period ends, the default), `advance` (pay at or before the start; the next payslip settles what happened), `same_month` (pay near the end; attendance counted up to the cut-off day). `pay_day`: day of month, 0 = last day; `pay_day_shift: before` moves a pay date off weekends and holidays to the working day before. `available: false` when pay timing is not installed. **Permission:** Payroll module and `view_payroll` not *none*. Responses: 200, 403 #### GET /payroll/{id}/payslip Payslip detail One payslip ready to render: the **work period** it pays for (`work_period.from`/`to`, and `counted_to`, the last day the slip itself counted) and the **pay date**, which differ with the company's pay timing (in arrears, in advance, same month). Earnings and deductions come as lines with a `source`: `basic`, `salary` (salary group components), `extra`, `additional`, `fixed_allowance`, or `adjustment` for previous-period adjustments. `adjustments` lists those adjustment lines with where they came from (`source_text`, e.g. unpaid leave dates or an expense claim). **Permission:** Payroll module and `view_payroll` not *none*, then the slip's own rule (as GET /payroll/{id}): at *all* any slip; otherwise only your own, from status *review* onwards, and slips of a payroll run only once the run is released. Anything else answers 404. Parameters: id (path, required) Responses: 200, 403, 404 ### Payroll Tax & Contributions Tax & contribution calculators by work country (Pakistan FBR/EOBI/PESSI, US federal W-4 + FICA + FUTA/SUTA + state, UK PAYE/NI/student loans/pension, AU PAYG + super, UAE GPSSA/ADPF, Saudi GOSI): company settings, employee tax inputs (W-4, tax code, NI letter, TFN declaration), the contribution register and each payslip's calculation. Figures are a starting point, not tax advice. #### GET /payroll-tax/settings Tax & contributions settings Company switch (`enabled`) and settings by country (PK, US, GB, AU, AE, SA). Admins and payroll settings managers. Responses: 200, 403 #### PUT /payroll-tax/settings Update Tax & contributions settings Send `enabled` and/or `countries` (only the countries/keys you send change). Turning on needs Country & compliance in your plan. Responses: 200, 403, 422 #### GET /payroll-tax/employees/{employee_id}/inputs Employee tax inputs The employee's work country (resolved from employee → work location → company), the input fields its calculator needs, saved values (defaults filled in) and which required ones are missing. Needs "Manage employee salary". Parameters: employee_id (path, required) Responses: 200, 403, 404 #### PUT /payroll-tax/employees/{employee_id}/inputs Save employee tax inputs Inputs for the employee's work country: US W-4 (`filing_status`, `multiple_jobs`, `dependents_amount`, `other_income`, `deductions`, `extra_withholding`, `exempt`, state fields), UK (`tax_code`, `week1_month1`, `ni_category`, `student_loan_plans`, `pension_opt_out`, `pension_opt_in`, `previous_pay`, `previous_tax`), AU (`tfn_declaration`, `residency`, `tax_free_threshold`, `stsl_debt`, `super_fund`, `extra_withholding`), PK (`previous_taxable_income`, `previous_tax_deducted`, `eobi_exempt`, `social_security_exempt`), AE (`emirati`, `pension_scheme`), SA (`saudi`, `gosi_registration_date`, `gosi_system`, `gosi_exempt`). Values not sent keep their saved value. Parameters: employee_id (path, required) Responses: 200, 403, 404, 422 #### GET /payroll-tax/register Contribution register Employee deductions and employer contributions per scheme (income tax, EOBI, PESSI, FICA, FUTA/SUTA, NI, pension, super, GPSSA, GOSI…) by pay date. With `month`: one row per payslip and scheme; without: totals per month. Admins and edit payroll = all. Parameters: year (query), month (query), scheme (query), status (query) Responses: 200, 403 #### GET /payroll-tax/payslips/{id} Payslip tax & contributions The calculator result stored on a payslip: employee lines, employer contributions (employer cost, not deducted), tax year and period. Payroll admins also get the explanation trail (rule, tax year, source) and warnings. `statutory` is null for payslips not calculated by Tax & contributions. Parameters: id (path, required) Responses: 200, 403, 404 ### Loans Employee loans — read-only. List and fetch loans with their approved amount, monthly installment, total payable, total paid and outstanding balance. Loans are created and approved through the in-app workflow, not the API. Filter by `user_id`, `status`, `loan_type_id`, etc. Add `?fields=repayments` to include the full repayment schedule. #### GET /loan/eligibility Eligibility (Loan) Performs the "Eligibility" action on a loan. Responses: 200, 401, 403, 422 #### GET /loan List Loans Returns a paginated list of Loans in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /loan Create a Loan Creates a new loan. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: code, user_id*, loan_type_id, amount_requested, purpose, required_on, status, applied_on, employee_consent, employee_signature, employee_signed_at, terms_snapshot, approved_amount, approved_installments, interest_rate, deduction_type, deduction_value, monthly_installment, total_payable, outstanding_balance, total_paid, approver_id, approver_signature, approver_signed_at, issue_date, rejection_reason, signed_agreement Responses: 200, 401, 403, 422 #### GET /loan/{loan} Get a Loan Fetches a single loan by its id, including its detail fields. Parameters: loan (path, required) Responses: 200, 401, 403, 422 #### DELETE /loan/{loan} Delete a Loan Permanently deletes a loan by its id. This cannot be undone. Parameters: loan (path, required) Responses: 200, 401, 403, 422 ### Loan Repayments Repayment schedule rows for loans — read-only. Each row is one installment with its due month, scheduled amount, paid amount and status (scheduled / deducted / paid / waived). Filter by `loan_id` to pull a single loan's schedule and see what is paid vs remaining. #### GET /loan-repayment List Loan Repayments Returns a paginated list of Loan Repayments in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /loan-repayment/{loan_repayment} Get a Loan Repayment Fetches a single loan repayment by its id, including its detail fields. Parameters: loan_repayment (path, required) Responses: 200, 401, 403, 422 ### Loan Types Loan products (amount limits, repayment months, interest) — read-only reference data. #### GET /loan-type List Loan Types Returns a paginated list of Loan Types in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /loan-type/{loan_type} Get a Loan Type Fetches a single loan type by its id, including its detail fields. Parameters: loan_type (path, required) Responses: 200, 401, 403, 422 ### Performance Reviews (to do, mine, team; respond; sign off), review cycles and templates (HR), feedback, improvement plans and the goal tree with weights and key-result sources. Same visibility rules as the Performance pages. #### GET /performance/reviews My reviews: to do, mine, my team What the caller must fill in (`to_do`, with due dates), their own reviews (`mine`) and their direct reports' (`team`). **Permission:** Performance module on; never client accounts. Reviews: HR, the employee, their manager or an invited reviewer (others get 404). Responses: 200, 401 #### GET /performance/reviews/{review_id} One review Stage, cycle, the template's questions, the caller's own forms (`my_responses`, with `editable`) and the other submitted answers they may read (`other_responses`): HR and the manager see everything; the employee sees manager and peer answers once the review is shared, with peers anonymous when the cycle says so. Scores and the manager's summary follow the same rule. **Permission:** Performance module on; never client accounts. Reviews: HR, the employee, their manager or an invited reviewer (others get 404). Parameters: review_id (path, required) Responses: 200, 404 #### POST /performance/reviews/{review_id}/respond Save or submit my review form Fill in your own form on this review. `mode: draft` saves, `mode: submit` submits (all required questions for your role must be answered; a manager must give `manager_rating`). HR completes the manager step for someone without a manager. **Permission:** Performance module on; never client accounts. Reviews: HR, the employee, their manager or an invited reviewer (others get 404). Parameters: review_id (path, required) Body: reviewer_type*, mode, answers, overall_comment, manager_rating, manager_summary, strengths, improvements Responses: 200, 403, 422 #### POST /performance/reviews/{review_id}/sign-off Sign off my review The employee closes their own review once it is shared (stage `sign_off`), with an optional comment. Fires the `performance_review.signed_off` event. Parameters: review_id (path, required) Body: employee_comment Responses: 200, 403 #### GET /performance/review-cycles Review cycles (HR) Every cycle with its dates, what it includes and live progress. **Permission:** HR (Performance management rights). Responses: 200, 403 #### GET /performance/review-templates Review templates (HR) Templates with their questions, weights and audiences. **Permission:** HR. Responses: 200, 403 #### GET /performance/feedback Feedback `tab=received` (default; your feedback except private notes), `given`, `team` (managers: your direct reports; HR: everyone), `wall` (public praise). **Permission:** Performance module on; never client accounts. Reviews: HR, the employee, their manager or an invited reviewer (others get 404). Parameters: tab (query) Responses: 200, 401 #### POST /performance/feedback Give feedback To a colleague (never yourself). `type`: praise, constructive or note (private notes are for managers and HR and are never shown to the person). `visibility`: recipient, manager or public (public only for praise). Fires `feedback.given` (not for notes). Body: to_user_id*, type*, visibility*, message* Responses: 200, 403, 422 #### GET /performance/pips Performance improvement plans HR sees all; others see their own plan and those of their team. Responses: 200, 401 #### GET /performance/pips/{pip_id} One improvement plan With reason, support and whether the caller can acknowledge it. Parameters: pip_id (path, required) Responses: 200, 404 #### POST /performance/pips/{pip_id}/acknowledge Acknowledge my improvement plan The employee confirms they have read their plan (once). Parameters: pip_id (path, required) Responses: 200, 404, 422 #### GET /performance/goal-tree Goal tree (OKRs) Every goal the caller may see as a tree: company → department → individual, children under their `parent_id`. Each goal has `weight`, computed `progress`, owners and key results with `progress_source` (manual, or synced from a project/tasks) and `source_project_id`. Finished goals older than 90 days are left out unless `completed=1`. Parameters: completed (query) Responses: 200, 401 #### GET /performance/goals/{goal_id} One goal Same shape as a goal-tree node (without children) plus the description. Parameters: goal_id (path, required) Responses: 200, 404 ### HR Documents Letters, offer letters and policies: the HR hub (issued documents, letter detail and PDF, policy compliance) and each employee's own documents (read, download, acknowledge). #### GET /hr-documents Issued documents (HR hub) Letters and offer letters across the company, newest first. Filters: `kind` (letter, offer), `type` (document type, e.g. experience, warning), `stage` (onboarding, employment, offboarding…), `status` (issued, viewed, awaiting_signature, signed, acknowledged, declined), `user_id`, `q`. **Permission:** the HR documents hub (view_letter / view_offer_letter at all, or policy/template management). Parameters: kind (query), type (query), stage (query), status (query), user_id (query), q (query) Responses: 200, 403 #### GET /hr-documents/policy-compliance Policy compliance Per published policy: audience, how many acknowledged, who is pending, %; `meta.overall_percent` for the company. Responses: 200, 403 #### GET /hr-documents/letters/{letter_id} One letter (HR) Status, dates and the rendered letter body (`html`, sanitised). **Permission:** view_letter at all. Parameters: letter_id (path, required) Responses: 200, 404 #### GET /hr-documents/letters/{letter_id}/pdf Download a letter (PDF) The letter on the company letterhead; a signed letter includes its signature certificate. **Permission:** view_letter at all. Parameters: letter_id (path, required) Responses: 200, 404 #### GET /my-documents My documents The caller's letters, offer letters and policies: `to_do` (to read, acknowledge or sign; `sign_url` opens the signing page) and `done`. Responses: 200, 401 #### GET /my-documents/letters/{letter_id} One of my letters Opening it marks it viewed, as on the web. Parameters: letter_id (path, required) Responses: 200, 404 #### GET /my-documents/letters/{letter_id}/pdf Download one of my letters (PDF) Your own letter as PDF. Parameters: letter_id (path, required) Responses: 200, 404 #### POST /my-documents/letters/{letter_id}/acknowledge Acknowledge one of my letters For letters that do not need a signature. Fires `hr_document.signed`. Parameters: letter_id (path, required) Responses: 200, 404, 422 ### Country & Compliance Jurisdictions (countries, states, provinces, emirates), dated legal rules with sources and your company overrides, public holiday calendars, work jurisdiction per location and employee, and the minimum wage check. Figures are a starting point, not legal advice. #### GET /compliance/jurisdictions Jurisdictions Countries and their regions (states, provinces, emirates, UK nations, free zones) that have rule data. Global list. Parameters: country (query) Responses: 200, 403 #### GET /compliance/jurisdictions/{jurisdiction} One jurisdiction With its regions. Parameters: jurisdiction (path, required) Responses: 200, 403, 404 #### GET /compliance/rules Legal rules (resolved) Every rule valid on `on` for the jurisdiction, with **your company's overrides applied** (company override → region → country). Each row has the value, effective dates, source and whether it has been checked (`verified`); `origin` is `company` for your override. Filter by key prefix, e.g. `min_wage.`, `leave.`, `contrib.`, `statutory_id.`. Figures are a starting point, not legal advice. Parameters: jurisdiction (query), prefix (query), on (query) Responses: 200, 403, 404, 422 #### GET /compliance/rules/{rule_key}/history Rule history All dated rows for one rule key along the jurisdiction chain (published + your overrides), newest first. Parameters: rule_key (path, required), jurisdiction (query) Responses: 200, 403 #### POST /compliance/overrides Override a rule for your company Adds (or replaces, for the same start date) your company's value for one rule. Earlier periods keep the rule valid then. **Permission:** Manage Country & Compliance = All. Body: jurisdiction_id*, rule_key*, value*, effective_from*, effective_to, notes, source_url Responses: 200, 403, 422 #### DELETE /compliance/overrides Reset a rule to the published value Removes all of your company's overrides of `rule_key` in that jurisdiction. Published rules are never changed. **Permission:** Manage Country & Compliance = All. Parameters: jurisdiction_id (query, required), rule_key (query, required) Responses: 200, 403 #### GET /compliance/holidays Public holiday calendar The jurisdiction's calendar for a year (a region includes its country's holidays). `exists` = already in your Holidays. Moon-dependent dates are flagged; UK dates come from the GOV.UK feed. Parameters: jurisdiction (query), year (query) Responses: 200, 403 #### POST /compliance/holidays/import Import holidays into the company Copies the calendar into Holidays. Dates you already have are skipped (safe to repeat). **Permission:** Manage Country & Compliance = All and Add Holiday. Body: jurisdiction_id*, year*, include_moon Responses: 200, 403 #### GET /compliance/company Company country & work locations Default jurisdiction and each work location's jurisdiction. Responses: 200, 403 #### PUT /compliance/company Set the company country / region `null` clears it. **Permission:** Manage Country & Compliance = All. Body: default_jurisdiction_id* Responses: 200, 403 #### PUT /compliance/locations/{address_id} Set a work location's jurisdiction Work locations are Settings → Offices & addresses. **Permission:** Manage Country & Compliance = All. Parameters: address_id (path, required) Body: jurisdiction_id* Responses: 200, 403, 404 #### GET /compliance/employee/{employee_id}/jurisdiction Employee's work jurisdiction The override (if any) and the resolved jurisdiction with `source`: employee, work_location, company or company_address. Parameters: employee_id (path, required) Responses: 200, 403, 404 #### PUT /compliance/employee/{employee_id}/jurisdiction Set an employee's work jurisdiction override `null` = follow the work location. **Permission:** Edit Employees = All or Manage Country & Compliance = All. Parameters: employee_id (path, required) Body: work_jurisdiction_id* Responses: 200, 403, 404 #### GET /compliance/employee/{employee_id}/minimum-wage Minimum wage check Compares a monthly salary with the minimum wage of the employee's jurisdiction on `on`. Hourly minimums are converted with the standard weekly hours. Advisory. **Permission:** Manage Employee Salary (all/added) or Manage Country & Compliance. Parameters: employee_id (path, required), monthly_salary (query, required), on (query) Responses: 200, 403, 422 #### GET /compliance/leave-policy Country leave policy (preview) The leave types the jurisdiction's `leave.*` rules would create or update, valid on `on`: days a year, paid or tiered pay (`pay_tiers`, percent of a day's pay), carry-forward and accumulation caps, accrual by hours worked (`accrual_hours_ratio` = hours worked per leave hour), service-based increase, eligibility (`effective_after` months, `allowed_probation`), gender and `hide_on_payslip`. `existing_id` = the leave type that would be updated. Read-only. **Permission:** Manage Leave Settings = All. Parameters: jurisdiction (query), on (query), limit_to_jurisdiction (query) Responses: 200, 403, 404 #### POST /compliance/leave-policy/apply Apply a country leave policy Creates or updates the company's leave types (never a second leave system) and recalculates balances. `keys` = which policies (omit for all). `limit_to_jurisdiction` = only employees working there see the types (use it when you have people in several countries). A future `effective_from` is stored as **scheduled** and applied on that date; managed types also move to a newer version of the law automatically from its effective date. **Permission:** Manage Leave Settings = All. Body: jurisdiction*, keys, effective_from, limit_to_jurisdiction Responses: 200, 403, 404, 422 #### GET /compliance/leave-policy/history Leave policy history Applied, scheduled and automatic (new law took effect) runs, newest first. **Permission:** Manage Leave Settings = All. Responses: 200, 403 #### GET /compliance/working-time/rules Working-time limits Daily/weekly limits, overtime thresholds and rates (incl. US-CA double time and 7th day, UAE night rate), overtime caps, breaks, weekly rest and Ramadan hours for an employee (their work jurisdiction, exempt flag and Ramadan flag) or a jurisdiction, on a date. **Permission:** View Attendance = All for others; anyone for themselves. Parameters: employee_id (query), jurisdiction (query), on (query) Responses: 200, 403, 404 #### GET /compliance/working-time/check Hours vs legal limits Attendance hours per employee with the legal overtime split (`overtime`, `double`, `night`, `rest_day`, weighted `pay_hours`), longest day/week, overtime this year, and `findings`: over_daily, over_weekly, average (UK 48 h), ot_year (PK 624 h, SA 720 h), no_rest_day, break. Severity `breach` = above the limit plus the allowed overtime. Max 93 days. Without `employee_id`, callers with View Attendance = All get every active employee (300 max). Parameters: employee_id (query), from (query), to (query), only_findings (query), with_days (query) Responses: 200, 403, 422 #### POST /compliance/working-time/overtime-check Check an overtime request Checks hours against the legal daily / weekly / yearly overtime caps (incl. other pending and approved requests), the exempt flag, attendance for the day, Ramadan hours and the employee's pay code rate vs the legal multiplier (`legal_multiplier`, blended when part is double or night time). `block` = true when the company blocks overtime above the cap. Body: employee_id, date*, hours*, type Responses: 200, 403, 422 #### GET /compliance/working-time/ramadan Ramadan dates and hours Expected Ramadan range for a year (moon-dependent: confirm the official dates; a company override of `calendar.ramadan` replaces them) and the reduced hours: UAE 2 h less a day for everyone, Saudi Arabia 6 h a day for employees who observe Ramadan. Parameters: jurisdiction (query), year (query) Responses: 200, 403 #### GET /compliance/working-time/settings Working-time settings Company switches. **Permission:** Manage Attendance Settings = All. Responses: 200, 403 #### PUT /compliance/working-time/settings Update working-time settings `legal_multiplier`: time-rate overtime pay codes never pay below the legal rate for the day. `block_over_cap`: refuse overtime requests/approvals above the legal cap. Send only what changes. **Permission:** Manage Attendance Settings = All. Body: attendance_warnings, overtime_warnings, block_over_cap, legal_multiplier, ramadan_hours Responses: 200, 403 #### PUT /compliance/employee/{employee_id}/working-time Set an employee's overtime exemption / Ramadan flag `overtime_exempt` (US FLSA exempt: no overtime due) and `observes_ramadan` (Saudi Ramadan hours). `null` clears. **Permission:** Manage Attendance Settings = All. Parameters: employee_id (path, required) Body: overtime_exempt, observes_ramadan Responses: 200, 403, 404, 422 #### GET /compliance/statutory-id-definitions Statutory ID definitions Which IDs a country / region needs (from the dated `statutory_id.*` legal rules, your company overrides applied). `kind`: number, check (right to work, I-9, VEVO), form (W-4, tax code, super fund) or ack. Passports (`managed_in: immigration`) are kept in the immigration records. Parameters: jurisdiction (query), all (query) Responses: 200, 403, 404 #### GET /compliance/employee/{employee_id}/statutory-ids Employee statutory IDs Every ID required/optional where the employee works (their resolved jurisdiction) with the saved record, plus records from other countries. **Values are masked** (`value` = null, `value_masked` = •••••-•••••••-1). Pass `reveal=1` to get `value`, `details` and `notes` — needs **View Statutory IDs = All** (or Owned for your own record). **Who can list:** yourself, View/Edit Statutory IDs = All, or View Employees = All. Parameters: employee_id (path, required), reveal (query) Responses: 200, 403, 404 #### POST /compliance/employee/{employee_id}/statutory-ids Add or update a statutory ID Creates the record for `code` (+ `country`) or updates it if the employee already has one. Validated per definition (format, check digits: TFN, ABN, Saudi ID / iqama). An expiry / next-check date creates or updates the employee's document expiry alert. **Permission:** Add / Edit Statutory IDs = All (or Owned for yourself). Parameters: employee_id (path, required) Body: code*, country, value, issued_on, expires_on, check_method, checked_on, check_result, next_check_on, alert_before_days, details, notes, verified, document_id Responses: 200, 403, 422 #### PUT /compliance/employee/{employee_id}/statutory-ids/{id} Update a statutory ID Send only what changes; omit `value` to keep the stored number. Right-to-work example: `{"check_method":"Home Office online check (share code)","checked_on":"2026-11-01","check_result":"time_limited","next_check_on":"2027-05-01"}`. **Permission:** Add / Edit Statutory IDs. Parameters: employee_id (path, required), id (path, required) Body: value, issued_on, expires_on, check_method, checked_on, check_result, next_check_on, alert_before_days, details, notes, verified, document_id Responses: 200, 403, 404 #### DELETE /compliance/employee/{employee_id}/statutory-ids/{id} Delete a statutory ID Removes the record and its expiry alert. A linked scan copy stays in the employee's documents. **Permission:** Add / Edit Statutory IDs. Parameters: employee_id (path, required), id (path, required) Responses: 200, 403, 404 #### PUT /compliance/employee/{employee_id}/work-eligibility Set nationality / residency / Ramadan hours Employee fields used by statutory rules: `nationality` (ISO2; GOSI Saudi/non-Saudi, GPSSA, Emiratisation/Nitaqat), `residency_status` (citizen, permanent_resident, work_visa, dependant_visa, other) and `observes_ramadan_hours` (reduced Ramadan hours; recorded instead of religion). `null` clears. Returns the statutory ID list. **Permission:** Edit Employees = All or Manage Country & Compliance. Parameters: employee_id (path, required) Body: nationality, residency_status, observes_ramadan_hours Responses: 200, 403, 422 ## Time Tracking ### Activity Tracker Manage activity tracker. #### GET /tracker-activity List Activity Tracker Returns a paginated list of Activity Tracker in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /tracker-activity/{tracker_activity} Get a Activity Tracker Fetches a single activity tracker by its id, including its detail fields. Parameters: tracker_activity (path, required) Responses: 200, 401, 403, 422 ### Break Logs Manage break logs. #### GET /break List Break Logs Returns a paginated list of Break Logs in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /break Create a Break Log Creates a new break log. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: user_id*, attendance_id, break_type_id, start_time*, end_time, duration_minutes, is_over_limit, note Responses: 200, 401, 403, 422 #### GET /break/{break} Get a Break Log Fetches a single break log by its id, including its detail fields. Parameters: break (path, required) Responses: 200, 401, 403, 422 #### PUT /break/{break} Update a Break Log Updates an existing break log by its id. Send only the fields you want to change; everything else stays as-is. Parameters: break (path, required) Body: user_id, attendance_id, break_type_id, start_time, end_time, duration_minutes, is_over_limit, note Responses: 200, 401, 403, 422 #### DELETE /break/{break} Delete a Break Log Permanently deletes a break log by its id. This cannot be undone. Parameters: break (path, required) Responses: 200, 401, 403, 422 ### Break Types Manage break types. #### GET /break-type List Break Types Returns a paginated list of Break Types in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /break-type Create a Break Type Creates a new break type. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: name*, time_limit, is_paid, color, status Responses: 200, 401, 403, 422 #### GET /break-type/{break_type} Get a Break Type Fetches a single break type by its id, including its detail fields. Parameters: break_type (path, required) Responses: 200, 401, 403, 422 #### PUT /break-type/{break_type} Update a Break Type Updates an existing break type by its id. Send only the fields you want to change; everything else stays as-is. Parameters: break_type (path, required) Body: name, time_limit, is_paid, color, status Responses: 200, 401, 403, 422 #### DELETE /break-type/{break_type} Delete a Break Type Permanently deletes a break type by its id. This cannot be undone. Parameters: break_type (path, required) Responses: 200, 401, 403, 422 ## Support ### Tickets Support tickets. #### GET /ticket-forms Public support forms The company's public ticket forms with their public URL and submission counts. People submit them on the web page (`public_url`), not through the API. **Permission:** manage_ticket_setting at all. Responses: 200, 401 #### GET /ticket-forms/{form_id} One support form Fields (with types, options, required), design and settings. Parameters: form_id (path, required) Responses: 200, 404 #### GET /ticket/me Me (Ticket) Performs the "Me" action on a ticket. Responses: 200, 401, 403, 422 #### GET /ticket List Tickets Returns a paginated list of Tickets in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /ticket Create a Ticket Creates a new ticket. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: user_id*, subject*, status, priority, agent_id, channel_id, type_id Responses: 200, 401, 403, 422 #### GET /ticket/{ticket} Get a Ticket Fetches a single ticket by its id, including its detail fields. Parameters: ticket (path, required) Responses: 200, 401, 403, 422 #### PUT /ticket/{ticket} Update a Ticket Updates an existing ticket by its id. Send only the fields you want to change; everything else stays as-is. Parameters: ticket (path, required) Body: user_id, subject, status, priority, agent_id, channel_id, type_id Responses: 200, 401, 403, 422 #### DELETE /ticket/{ticket} Delete a Ticket Permanently deletes a ticket by its id. This cannot be undone. Parameters: ticket (path, required) Responses: 200, 401, 403, 422 ### Ticket Replies Manage ticket replies. #### POST /ticket-reply-file Ticket Reply File (Ticket Reply) Performs the "Ticket Reply File" action on a ticket reply. Responses: 200, 401, 403, 422 #### GET /ticket-reply List Ticket Replies Returns a paginated list of Ticket Replies in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /ticket-reply Create a Ticket Reply Creates a new ticket reply. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: ticket_id*, user_id*, message Responses: 200, 401, 403, 422 #### GET /ticket-reply/{ticket_reply} Get a Ticket Reply Fetches a single ticket reply by its id, including its detail fields. Parameters: ticket_reply (path, required) Responses: 200, 401, 403, 422 #### PUT /ticket-reply/{ticket_reply} Update a Ticket Reply Updates an existing ticket reply by its id. Send only the fields you want to change; everything else stays as-is. Parameters: ticket_reply (path, required) Body: ticket_id, user_id, message Responses: 200, 401, 403, 422 #### DELETE /ticket-reply/{ticket_reply} Delete a Ticket Reply Permanently deletes a ticket reply by its id. This cannot be undone. Parameters: ticket_reply (path, required) Responses: 200, 401, 403, 422 ### Ticket Groups Manage ticket groups. #### GET /ticket-group List Ticket Groups Returns a paginated list of Ticket Groups in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 ### Ticket Channels Manage ticket channels. #### GET /ticket-channel List Ticket Channels Returns a paginated list of Ticket Channels in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 ### Ticket Types Manage ticket types. #### GET /ticket-type List Ticket Types Returns a paginated list of Ticket Types in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 ### Notice Board Manage notice board. #### GET /notice List Notice Board Returns a paginated list of Notice Board in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /notice Create a Notice Board Creates a new notice board. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: to, heading*, description, department_id Responses: 200, 401, 403, 422 #### GET /notice/{notice} Get a Notice Board Fetches a single notice board by its id, including its detail fields. Parameters: notice (path, required) Responses: 200, 401, 403, 422 #### PUT /notice/{notice} Update a Notice Board Updates an existing notice board by its id. Send only the fields you want to change; everything else stays as-is. Parameters: notice (path, required) Body: to, heading, description, department_id Responses: 200, 401, 403, 422 #### DELETE /notice/{notice} Delete a Notice Board Permanently deletes a notice board by its id. This cannot be undone. Parameters: notice (path, required) Responses: 200, 401, 403, 422 ### Events Manage events. #### GET /event/{event}/attendees Attendees (Event) Performs the "Attendees" action on a event. Parameters: event (path, required) Responses: 200, 401, 403, 422 #### POST /event/{event}/rsvp Reply to an event invitation (RSVP) The caller's own reply: `going`, `maybe` or `declined`. Only people invited to the event can reply, and not to a cancelled event (403). People who declined get no reminder. Attendee lists (`GET event/{id}/attendees`, `GET me/calendar`) carry each person's `rsvp` (null = no reply yet) and `rsvp_at`. Parameters: event (path, required) Body: rsvp* Responses: 200, 403, 404, 422 #### GET /event List Events Returns a paginated list of Events in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /event Create a Event Creates a new event. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: event_name*, where*, description*, start_date_time*, end_date_time*, label_color*, status, note*, host, event_link, repeat, repeat_every, repeat_cycles, repeat_type, send_reminder, remind_time, remind_type, departments Responses: 200, 401, 403, 422 #### GET /event/{event} Get a Event Fetches a single event by its id, including its detail fields. Parameters: event (path, required) Responses: 200, 401, 403, 422 #### PUT /event/{event} Update a Event Updates an existing event by its id. Send only the fields you want to change; everything else stays as-is. Parameters: event (path, required) Body: event_name, where, description, start_date_time, end_date_time, label_color, status, note, host, event_link, repeat, repeat_every, repeat_cycles, repeat_type, send_reminder, remind_time, remind_type, departments Responses: 200, 401, 403, 422 #### DELETE /event/{event} Delete a Event Permanently deletes a event by its id. This cannot be undone. Parameters: event (path, required) Responses: 200, 401, 403, 422 #### GET /me/calendar Me (Event) Performs the "Me" action on a event. Responses: 200, 401, 403, 422 #### GET /me/calendar-feed My private calendar subscription link (.ics) A personal iCalendar feed of your own events (60 days back to one year ahead) for Google Calendar, Outlook or Apple Calendar. The link is the credential: keep it private, and reset it if it leaks. Responses: 200, 401 #### POST /me/calendar-feed/reset Reset my calendar subscription link Creates a new private link; the old one stops working at once. Calendars subscribed to the old link must be re-subscribed. Responses: 200, 401 #### GET /me/google-calendar My Google Calendar connection Your OWN Google Calendar connection (the same as My Profile → Google Calendar). It only ever covers the caller: items that involve you (tasks assigned to you, appointments you host, your approved leave, events and interviews you take part in, optional holidays) are copied to your calendar and kept up to date. Tokens are never returned. `status`: connected | error (Google access removed — reconnect) | disconnected. Connecting needs Google's consent screen: open `connect_url` in a browser while signed in to BusinessHRM. Responses: 200, 403 #### PUT /me/google-calendar Choose what is added to my Google Calendar Your OWN Google Calendar connection (the same as My Profile → Google Calendar). It only ever covers the caller: items that involve you (tasks assigned to you, appointments you host, your approved leave, events and interviews you take part in, optional holidays) are copied to your calendar and kept up to date. Tokens are never returned. `status`: connected | error (Google access removed — reconnect) | disconnected. Send only the types to change. A type must be in `available_types` (your company plan). Changing a type re-syncs your calendar in the background (switched-off types are removed from it). Body: sync* Responses: 200, 422, 403, 404 #### DELETE /me/google-calendar Disconnect my Google Calendar Removes the items BusinessHRM added to your calendar (in the background), revokes access at Google and forgets the token. Responses: 200, 403, 404 #### POST /me/google-calendar/sync Sync my Google Calendar now Re-checks every item in the sync window (30 days back, a year ahead) and updates your calendar in the background. Responses: 200, 422, 403, 404 ### Feedback Hub Website feedback, page pins (with element selectors and signed screenshots) and voting-board ideas from your Feedback Hub widgets. Built for AI assistants: every item has an AI-readable shape and a ready-made fix prompt. Same tools are available over MCP. #### GET /feedback List feedback Website feedback, page pins and board ideas, newest first, in an AI-friendly shape (page, element locators, environment, console errors, signed screenshot link). **Access:** the Feedback module on for the workspace and the caller's Feedback permission (`view_feedback` or `manage_feedback`), or membership of the project a widget is linked to (then only that widget's items). API keys need the `feedback` scope. Parameters: status (query), project (query), widget (query), url (query), url_contains (query), route (query), type (query), priority (query), kind (query), search (query), limit (query), offset (query) Responses: 200, 403 #### GET /feedback/pins Pins on one page Every page comment (pin) on a page, with element locators and pin position inside the element. Pass `url`. **Access:** the Feedback module on for the workspace and the caller's Feedback permission (`view_feedback` or `manage_feedback`), or membership of the project a widget is linked to (then only that widget's items). API keys need the `feedback` scope. Parameters: status (query), project (query), widget (query), url (query), url_contains (query), route (query), type (query), priority (query), kind (query), search (query) Responses: 200, 422 #### GET /feedback/export Export feedback (JSON or Markdown) Up to 500 items matching the filters. `format=md` returns Markdown ready to paste into an AI assistant. **Access:** the Feedback module on for the workspace and the caller's Feedback permission (`view_feedback` or `manage_feedback`), or membership of the project a widget is linked to (then only that widget's items). API keys need the `feedback` scope. Parameters: status (query), project (query), widget (query), url (query), url_contains (query), route (query), type (query), priority (query), kind (query), search (query), format (query) Responses: 200, 401 #### GET /feedback/summary Summary of open feedback Counts by type, priority, status and page, plus an AI summary when the workspace has AI set up (pass `ai=0` to skip it). **Access:** the Feedback module on for the workspace and the caller's Feedback permission (`view_feedback` or `manage_feedback`), or membership of the project a widget is linked to (then only that widget's items). API keys need the `feedback` scope. Parameters: status (query), project (query), widget (query), url (query), url_contains (query), route (query), type (query), priority (query), kind (query), search (query), ai (query) Responses: 200, 401 #### GET /feedback/{id} Get one feedback item The full item: page, element (selector, XPath, ids, component, masked HTML), pin, environment, console errors, replies (incl. internal notes) and a ready-to-paste `fix_prompt`. **Access:** the Feedback module on for the workspace and the caller's Feedback permission (`view_feedback` or `manage_feedback`), or membership of the project a widget is linked to (then only that widget's items). API keys need the `feedback` scope. Parameters: id (path, required) Responses: 200, 404 #### GET /feedback/{id}/screenshot Screenshot links Short-lived signed links (10 minutes) to the section screenshot and the whole-page context image. Screenshots are private; they expire under the plan's retention rule. **Access:** the Feedback module on for the workspace and the caller's Feedback permission (`view_feedback` or `manage_feedback`), or membership of the project a widget is linked to (then only that widget's items). API keys need the `feedback` scope. Parameters: id (path, required) Responses: 200, 404 #### POST /feedback/{id}/reply Reply to feedback Adds a reply. `internal: true` = team-only note; otherwise the reporter sees it in the widget and gets one e-mail. Replies are copied to the linked task. **Access:** the Feedback module on for the workspace and the caller's Feedback permission (`view_feedback` or `manage_feedback`), or membership of the project a widget is linked to (then only that widget's items). API keys need the `feedback` scope. Parameters: id (path, required) Body: text*, internal Responses: 200, 422 #### POST /feedback/{id}/status Change status Moves the item (and its task on the Feedback board) to a status. **Access:** the Feedback module on for the workspace and the caller's Feedback permission (`view_feedback` or `manage_feedback`), or membership of the project a widget is linked to (then only that widget's items). API keys need the `feedback` scope. Parameters: id (path, required) Body: status*, note Responses: 200, 422 #### POST /feedback/{id}/resolve Resolve feedback Marks the item resolved (task moves to Done; the reporter sees "Resolved" and gets one e-mail). An optional `note` is saved as an internal note, or as a public reply with `public: true`. **Access:** the Feedback module on for the workspace and the caller's Feedback permission (`view_feedback` or `manage_feedback`), or membership of the project a widget is linked to (then only that widget's items). API keys need the `feedback` scope. Parameters: id (path, required) Body: note, public Responses: 200, 401 #### POST /feedback/{id}/to-task Create the task Creates (or returns) the task on the widget project's Feedback board, with the screenshot attached. **Access:** the Feedback module on for the workspace and the caller's Feedback permission (`view_feedback` or `manage_feedback`), or membership of the project a widget is linked to (then only that widget's items). API keys need the `feedback` scope. Parameters: id (path, required) Responses: 200, 422 ## Compliance ### Grievance Cases Confidential grievance, harassment, discrimination and bullying register (Workplace registers module). Row-level access: reporter, assigned handlers, View Confidential Cases. Anonymous reporters are never exposed. #### GET /workplace-case List grievance & harassment cases Cases the caller may see, newest first. **Who sees a case:** the reporter, assigned handlers/committee, people with `view_confidential_cases` = *all*, and — for cases NOT marked confidential — people with `view_workplace_cases` = *all* (except the person the case is about). Anything else answers 404. **Hidden fields:** an anonymous case never returns who reported it (`reporter` = "Anonymous", `reporter_user_id` = null). Private notes, handlers, priority and SLA dates are returned only to handlers, case editors and `view_confidential_cases`; never to the reporter. **Module:** Workplace registers (in your plan). Parameters: status (query), category_id (query), view (query), search (query), limit (query), offset (query) Responses: 200, 403 #### POST /workplace-case Report a concern / log a case Creates a case numbered CASE-0001, CASE-0002… with acknowledgement and resolution due dates from the register settings. The default committee is assigned and notified (or everyone with `view_confidential_cases` when none is set). **Permission:** `add_workplace_cases` = *all* (employees have it by default). `on_behalf` needs `view_confidential_cases`, `manage_workplace_cases` or `edit_workplace_cases` = *all*. Body: subject*, description*, category_id, is_anonymous, is_confidential, against_user_id, against_name, incident_date, location, priority, on_behalf, reporter_user_id Responses: 200, 403, 422 #### GET /workplace-case/{id} Get a case Full case. Opening it writes an entry in the HR audit log (case staff only). **Who sees a case:** the reporter, assigned handlers/committee, people with `view_confidential_cases` = *all*, and — for cases NOT marked confidential — people with `view_workplace_cases` = *all* (except the person the case is about). Anything else answers 404. **Hidden fields:** an anonymous case never returns who reported it (`reporter` = "Anonymous", `reporter_user_id` = null). Private notes, handlers, priority and SLA dates are returned only to handlers, case editors and `view_confidential_cases`; never to the reporter. Parameters: id (path, required) Responses: 200, 404 #### PUT /workplace-case/{id} Edit a case Partial update. **Permission:** an assigned handler/committee member, or `edit_workplace_cases` = *all* (the reporter can never edit). `is_confidential` needs `manage_workplace_cases`/`edit_workplace_cases` = *all* or `view_confidential_cases`; anonymous cases always stay confidential. Parameters: id (path, required) Body: subject, description, category_id, is_confidential, against_user_id, against_name, incident_date, location, priority, outcome Responses: 200, 403, 404, 422 #### DELETE /workplace-case/{id} Delete a case **Permission:** `delete_workplace_cases` = *all* and able to see the case. Attachments are deleted too. Parameters: id (path, required) Responses: 200, 403, 404 #### POST /workplace-case/{id}/status Move a case through the workflow Workflow: received → investigating / dismissed; investigating → resolved / dismissed; resolved or dismissed → closed (or back to investigating); closed → investigating. Resolving or dismissing needs an `outcome` (shown to the reporter). A named reporter is notified. **Permission:** handler or `edit_workplace_cases` = *all*. Parameters: id (path, required) Body: status*, outcome, note Responses: 200, 403, 404, 422 #### POST /workplace-case/{id}/acknowledge Acknowledge receipt Sets acknowledged_at (stops the acknowledgement reminder) and tells a named reporter. **Permission:** handler or `edit_workplace_cases` = *all*. Parameters: id (path, required) Responses: 200, 403, 404 #### POST /workplace-case/{id}/handlers Assign handlers / committee Replaces the list. The reporter and the person the case is about are ignored. New people are notified. **Permission:** `manage_workplace_cases` or `edit_workplace_cases` = *all*. Parameters: id (path, required) Body: handler_ids, committee_ids Responses: 200, 403, 404 #### POST /workplace-case/{id}/notes Add a private case note Never shown to the reporter. Encrypted at rest. **Permission:** handler or `edit_workplace_cases` = *all*. Parameters: id (path, required) Body: note* Responses: 200, 403, 404 ### Safety Incidents Safety incident register: injuries, near misses, corrective actions, reportable-to-authority flag. #### GET /safety-incident List safety incidents Newest first. **Permission:** `view_safety_incidents` — *all*, *added* (added by me), *owned* (reported by me, I am involved, or I own a corrective action) or *both*. Parameters: status (query), severity (query), incident_type (query), reportable (query), from (query), to (query), search (query), limit (query), offset (query) Responses: 200, 403 #### POST /safety-incident Report an incident Numbered INC-0001… Safety officers from the register settings (or everyone with `manage_safety_incidents`) are notified; action owners are notified. **Permission:** `add_safety_incidents` = *all* (employees have it by default). `actions` are kept only for `edit_safety_incidents` or `manage_safety_incidents` = *all*. Body: title*, occurred_at*, company_address_id, location_detail, incident_type*, severity*, injury_type, description*, immediate_action, root_cause, is_reportable, authority_name, authority_reference, reported_to_authority_on, people, actions Responses: 200, 403, 422 #### GET /safety-incident/{id} Get an incident With people, corrective actions and attachments. Parameters: id (path, required) Responses: 200, 404 #### PUT /safety-incident/{id} Edit an incident Partial update; `people` / `actions` replace their lists when sent. **Permission:** `edit_safety_incidents` = *all*, or *added* for incidents you added. Parameters: id (path, required) Body: title, occurred_at, company_address_id, location_detail, incident_type, severity, injury_type, description, immediate_action, root_cause, is_reportable, authority_name, authority_reference, reported_to_authority_on, people, actions Responses: 200, 403, 404, 422 #### DELETE /safety-incident/{id} Delete an incident **Permission:** `delete_safety_incidents` = *all*. Parameters: id (path, required) Responses: 200, 403, 404 #### POST /safety-incident/{id}/status Change incident status reported → investigating → actions_pending → closed. Closing needs every corrective action done. **Permission:** as for edit. Parameters: id (path, required) Body: status* Responses: 200, 403, 404, 422 ### HR Audit Log Read-only company audit trail of HR and payroll changes with before/after values. #### GET /hr-audit-log List HR / payroll audit entries Read-only, newest first: employee details and salary changes, attendance edits made by someone else (before/after times), leave decisions, payroll run status changes and payslip edits, statutory settings, privacy requests and workplace register actions. Entries cannot be changed or deleted (only the company's retention window removes old ones). **Permission:** `view_audit_log` = *all* (admins by default). Parameters: category (query), actor_id (query), employee_id (query), subject_type (query), subject_id (query), from (query), to (query), search (query), limit (query), offset (query) Responses: 200, 403 #### GET /hr-audit-log/{id} Get an audit entry One entry with every changed field before and after. **Permission:** `view_audit_log` = *all*. Parameters: id (path, required) Responses: 200, 403, 404 ### Privacy Requests Employee data export and erasure / anonymisation requests (GDPR). #### GET /privacy-request/my-data Download my personal data (ZIP) A ZIP of JSON files (profile, employment, emergency contacts, bank accounts, documents list, leave, attendance, salary, payslips, loans…). Passwords, tokens and stored-file keys are never included. Logged in the HR audit log. Responses: 200, 403 #### GET /privacy-request List privacy requests Your own erasure / anonymisation requests. GDPR managers (`manage_gdpr_setting` = *all*) can pass `scope=company` to see every employee request. Parameters: scope (query), status (query), limit (query), offset (query) Responses: 200, 403 #### POST /privacy-request Ask for anonymisation or erasure Employees only, when Settings → GDPR → Right to erasure → "Let employees download their data and request erasure" is on. One pending request at a time. GDPR managers are notified. **anonymise**: name, e-mail, phone, photo, address, birthday and emergency contacts removed; bank and ID numbers masked; login disabled. **erase**: also deletes HR documents, passport/visa rows and bank accounts. Payroll, attendance and leave history is always kept (unnamed). Body: request_type*, reason, confirm* Responses: 200, 403, 422 #### POST /privacy-request/{id}/cancel Cancel my pending request Only your own, only while pending. Parameters: id (path, required) Responses: 200, 404 #### POST /privacy-request/{id}/decide Approve or reject an employee request Approval runs the anonymiser immediately and cannot be undone; an admin account or your own request cannot be approved. Every decision is in the HR audit log. **Permission:** `manage_gdpr_setting` = *all*. Parameters: id (path, required) Body: decision*, note Responses: 200, 403, 404, 422 ## Add-ons ### Webhooks Webhook triggers. A trigger links an entity + event (e.g. Task / completed) to a destination URL. When the event happens in your workspace, BusinessHRM POSTs the record to that destination. **First create a destination in the app under Settings → Webhooks, then create triggers here** referencing that destination's id. #### GET /webhook-events Webhook event catalogue Every `entity` and `event` a webhook trigger can subscribe to, with a dotted `type` (e.g. `leave.approved`). This is the same event list the Webhooks settings screen and Automation triggers use. **Permission:** `view_webhooks`. Responses: 200, 403 #### GET /webhook-endpoint Webhook endpoints Your destinations with their events, status and last delivery. **Permission:** view_webhooks at all. Responses: 200, 401 #### POST /webhook-endpoint Create a webhook endpoint `events`: types like `leave.approved` (or `Leave:approved`) from GET webhook-events. `type`: custom (signed JSON), discord or slack (their webhook URLs only). URLs must be public https (no private or internal addresses). The signing secret is returned **once**, here. Optional `headers` {name: value} and `filters`. **Permission:** add_webhooks at all. Body: name*, url*, type, events*, description, headers, filters, verify_ssl, status Responses: 200, 422 #### GET /webhook-endpoint/{endpoint_id} One webhook endpoint With `deliveries_last_7_days` by status. Parameters: endpoint_id (path, required) Responses: 200, 404 #### PUT /webhook-endpoint/{endpoint_id} Update a webhook endpoint Send only what changes; `events` replaces the list. **Permission:** edit_webhooks at all. Parameters: endpoint_id (path, required) Body: name, url, events, status Responses: 200, 401 #### DELETE /webhook-endpoint/{endpoint_id} Delete a webhook endpoint **Permission:** delete_webhooks at all. Parameters: endpoint_id (path, required) Responses: 200, 401 #### POST /webhook-endpoint/{endpoint_id}/rotate-secret Rotate the signing secret Returns the new secret once. During the grace period (24 h) deliveries carry two `v1=` signatures, old and new, so you can switch without missing events. Parameters: endpoint_id (path, required) Responses: 200, 401 #### POST /webhook-endpoint/{endpoint_id}/test Send a test event Sends a sample of `type` (or the first subscribed event) now, marked `test: true`, and returns the receiver's answer. Max 20 tests/resends per minute. Parameters: endpoint_id (path, required) Body: type Responses: 200, 401 #### GET /webhook-endpoint/{endpoint_id}/deliveries Deliveries of an endpoint Newest first, cursor paged (`before_id`). `status`: pending, succeeded, retrying, failed. **Permission:** view_webhooks_logs at all. Parameters: endpoint_id (path, required), status (query), before_id (query), limit (query) Responses: 200, 401 #### GET /webhook-delivery/{delivery_id} One delivery With the request body sent, the receiver's response (first 4 KB) and every attempt. Parameters: delivery_id (path, required) Responses: 200, 404 #### POST /webhook-delivery/{delivery_id}/resend Resend a delivery Sends the same payload again as a new delivery. **Permission:** edit_webhooks at all. Parameters: delivery_id (path, required) Responses: 200, 401 #### GET /webhook List Webhooks Returns a paginated list of Webhooks in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /webhook Create a Webhook Creates a new webhook. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: entity*, event*, webhooks_setting_id* Responses: 200, 401, 403, 422 #### GET /webhook/{webhook} Get a Webhook Fetches a single webhook by its id, including its detail fields. Parameters: webhook (path, required) Responses: 200, 401, 403, 422 #### PUT /webhook/{webhook} Update a Webhook Updates an existing webhook by its id. Send only the fields you want to change; everything else stays as-is. Parameters: webhook (path, required) Body: entity, event, webhooks_setting_id Responses: 200, 401, 403, 422 #### DELETE /webhook/{webhook} Delete a Webhook Permanently deletes a webhook by its id. This cannot be undone. Parameters: webhook (path, required) Responses: 200, 401, 403, 422 ### Messages Manage messages. #### GET /userchat/message-setting Message Setting (Message) Performs the "Message Setting" action on a message. Responses: 200, 401, 403, 422 #### GET /userchat/user-list User List (Message) Performs the "User List" action on a message. Responses: 200, 401, 403, 422 #### GET /userchat/messages/{userid} Direct messages with one person One 1:1 conversation. Pass `order=created_at desc`. Cursor paging: `before_id` = the oldest id you have (older page) or `after_id` = the newest id you have (only new messages); 20 per page unless `limit` says otherwise. Without either it behaves as before (offset paging). Parameters: userid (path, required), before_id (query), after_id (query), limit (query), order (query), fields (query) Responses: 200, 401 #### POST /userchat/messages/{userid}/read Mark Seen (Message) Performs the "Mark Seen" action on a message. Parameters: userid (path, required) Responses: 200, 401, 403, 422 #### GET /userchat/recipients Recipients (Message) Performs the "Recipients" action on a message. Responses: 200, 401, 403, 422 #### POST /userchat/{message}/files Store File (Message) Performs the "Store File" action on a message. Parameters: message (path, required) Responses: 200, 401, 403, 422 #### DELETE /userchat/{message}/files/{file_id} Destroy File (Message) Performs the "Destroy File" action on a message. Parameters: message (path, required), file_id (path, required) Responses: 200, 401, 403, 422 #### GET /userchat List Messages Returns a paginated list of Messages in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /userchat Create a Message Creates a new message. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: to*, message*, message_seen Responses: 200, 401, 403, 422 #### GET /userchat/{userchat} Get a Message Fetches a single message by its id, including its detail fields. Parameters: userchat (path, required) Responses: 200, 401, 403, 422 #### PUT /userchat/{userchat} Update a Message Updates an existing message by its id. Send only the fields you want to change; everything else stays as-is. Parameters: userchat (path, required) Body: to, message, message_seen Responses: 200, 401, 403, 422 #### DELETE /userchat/{userchat} Delete a Message Permanently deletes a message by its id. This cannot be undone. Parameters: userchat (path, required) Responses: 200, 401, 403, 422 ### Chat Direct messages, groups and channels in one model: cursor-paged messages, threads, reactions, pins, per-person read state, mentions, search and realtime (Pusher protocol) connection + channel auth. Client accounts keep using Messages (userchat). #### GET /chat/conversations My conversations with unread counts Every conversation the caller can read: `direct` (1:1, `conversation_id` = the other person's user id), `group` and `channel`, newest activity first, each with `unread` and `last_message_id`. `meta.unread_total` is the badge number. **Permission:** the Messages module; groups and channels also need the group chat add-on. Client accounts use the `userchat` endpoints instead (403 here). Responses: 200, 403 #### GET /chat/messages Messages in one conversation (cursor paged) Top-level messages of one conversation, newest first. Thread replies are not included (see `reply_count` and the thread endpoint). **Paging is a cursor:** pass `before_id` = `meta.next_before_id` for the next older page, or `after_id` = the newest id you have to fetch only new messages (returned oldest first). `limit` 1-100, default 30. Each message carries its author, files, `reactions` (emoji, count, whether one is yours, and `users` = who reacted), `mentions`, `reply_count`, `is_pinned`, `is_edited`. A conversation you cannot read answers 404. Parameters: type (query, required), conversation_id (query, required), before_id (query), after_id (query), limit (query) Responses: 200, 404, 422 #### POST /chat/messages Send a message Sends to a direct chat, group or channel; with `parent_id` it is a thread reply instead. `mention_user_ids` notifies those people (only members of the conversation are kept). A direct message needs the recipient to be someone you may message under the company's message settings (403 otherwise). Send an `Idempotency-Key` header to retry safely. Body: type, conversation_id, message*, parent_id, mention_user_ids Responses: 200, 403, 404, 422 #### GET /chat/messages/{message_id}/thread A thread: root message and replies Replies oldest first. Page forward with `after_id` = `meta.next_after_id`. Passing a reply id opens its thread. Parameters: message_id (path, required), after_id (query), limit (query) Responses: 200, 404 #### POST /chat/messages/{message_id}/thread Reply in a thread Parameters: message_id (path, required) Body: message*, mention_user_ids Responses: 200, 404 #### POST /chat/messages/{message_id}/reactions Toggle my reaction Adds the emoji as your reaction, or removes it if you already reacted with it. Returns the message's reactions. Parameters: message_id (path, required) Body: emoji* Responses: 200, 404, 422 #### GET /chat/messages/{message_id}/reactions Reactions on a message, with who reacted Parameters: message_id (path, required) Responses: 200, 404 #### DELETE /chat/messages/{message_id}/reactions Remove my reaction Parameters: message_id (path, required) Body: emoji* Responses: 200, 404 #### PUT /chat/messages/{message_id} Edit my message Only the sender can edit. The message is marked `is_edited`. Parameters: message_id (path, required) Body: message* Responses: 200, 403, 404 #### DELETE /chat/messages/{message_id} Delete a message The sender, or an admin who can see the conversation. Parameters: message_id (path, required) Responses: 200, 403, 404 #### GET /chat/unread-counts Unread count per conversation Only conversations with unread messages are listed; `meta.unread_total` is the sum. Responses: 200, 401 #### POST /chat/messages/{message_id}/pin Pin or unpin a message Toggles. Any member of the conversation may pin, as in the app. Parameters: message_id (path, required) Responses: 200, 404 #### GET /chat/messages/{message_id}/seen-by Who has read a message Groups and channels: people whose read position is at or past this message (up to 50, excluding the author and you). Direct chats: the other person once they have read it. Parameters: message_id (path, required) Responses: 200, 404 #### GET /chat/pins Pinned messages of a conversation Parameters: type (query, required), conversation_id (query, required) Responses: 200, 404 #### POST /chat/read Mark a conversation read (for me) Read state is per person in groups and channels: marking read never clears it for anyone else. Members see a read receipt over realtime (`chat.read`). Body: type*, conversation_id* Responses: 200, 404 #### GET /chat/unread My total unread count Responses: 200, 401 #### GET /chat/mentions Messages that mention me Newest first, only from conversations you can still read. Cursor paged with `before_id` / `limit`. Parameters: before_id (query), limit (query) Responses: 200, 401 #### GET /chat/saved My saved messages Messages I saved for later, newest saved first, only from conversations I can still read. Each row has `saved_id` / `saved_at`; page with `before_id` = the last `saved_id`. Parameters: before_id (query), limit (query) Responses: 200, 401 #### POST /chat/messages/{message_id}/save Save a message for later Parameters: message_id (path, required) Responses: 200, 404 #### DELETE /chat/messages/{message_id}/save Remove a message from saved Parameters: message_id (path, required) Responses: 200, 404 #### POST /chat/read-all Mark every conversation as read Direct messages, groups and channels I can see. My other devices get a `chat.activity` read event. Responses: 200, 401 #### GET /chat/presence Online status and last seen For up to 100 people in my company. `online` = active in the last 5 minutes. Parameters: user_ids (query) Responses: 200, 401 #### GET /chat/starred My starred conversations Starred conversations I can still open, with name, unread count and url. Responses: 200, 401 #### POST /chat/starred Star or unstar a conversation Toggles unless `starred` is given. Body: type*, conversation_id*, starred Responses: 200, 404 #### GET /chat/scheduled My scheduled messages Pending by default (`status=sent|failed|cancelled` for others). Optional `type` + `conversation_id` filter. Parameters: type (query), conversation_id (query), status (query) Responses: 200, 401 #### POST /chat/scheduled Schedule a message Posted as you at `send_at` (30 s to 1 year ahead) by the scheduler, with mentions, notifications and push. Access is checked again when it is sent. Body: type*, conversation_id*, message*, send_at*, mention_user_ids Responses: 200, 401 #### PUT /chat/scheduled/{scheduled_id} Change a scheduled message Parameters: scheduled_id (path, required) Body: message, send_at Responses: 200, 422 #### DELETE /chat/scheduled/{scheduled_id} Cancel a scheduled message Parameters: scheduled_id (path, required) Responses: 200, 401 #### GET /chat/messages/{message_id}/edits Edit history of a message Earlier versions, newest first. Parameters: message_id (path, required) Responses: 200, 401 #### GET /chat/search Search messages Text search over every conversation you can read, or one conversation when `type` + `conversation_id` are given. Cursor paged. Parameters: q (query, required), type (query), conversation_id (query), before_id (query), limit (query) Responses: 200, 422 #### POST /chat/active I have this conversation open Send every ~25 seconds while a conversation is on screen (the web sends the same heartbeat). While it is fresh (75 s) you get no bell, push or e-mail for that conversation, because you are reading it. Send with no body when the conversation closes or the app goes to the background. `type` direct|group|channel; for direct, `conversation_id` is the other person's user id. Body: type, conversation_id Responses: 200, 404 #### GET /chat/realtime Realtime connection info How to receive live chat events. The server speaks the Pusher protocol (any Pusher client works: pusher-js, Laravel Echo, pusher-websocket-swift, the Flutter/Java Pusher clients). Connect with `key`/`host`/`port`/`tls`/`path`, then subscribe to your `user_channel` (new-message and read badges everywhere) and to the open conversation's channel built from `channel_patterns`. Private channels are authorised by POST /chat/realtime/auth with your bearer token. Events: `chat.message` {action new|changed|deleted, type, id, parent_id, from, group_id, channel_id, to}, `chat.activity` (on your user channel), `chat.read` {type, user_id, last_read_id}. Events carry ids only: fetch the message with GET /chat/messages?after_id=. When `enabled` is false, poll GET /chat/messages?after_id= every `polling_fallback_seconds`. Responses: 200, 401 #### POST /chat/realtime/auth Authorise a realtime channel Pusher-protocol channel authorisation: send the `socket_id` your client got on connect and the private `channel_name`. Returns `{"auth": "key:signature"}` exactly as Pusher clients expect (point the client's auth endpoint here with your bearer token). Only your own user channel and conversations you can read are signed; anything else answers 403. 404 when live updates are off. Body: socket_id*, channel_name* Responses: 200, 403, 404 ## Account & General ### Company Endpoints for company. #### GET /company List Company Returns a paginated list of Company in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /branding Branding (theme the app) Colours, font, density, light/dark mode and logo URLs set in Settings → Branding, so an app can look like the web app. Any signed-in person, clients included. Responses: 200, 401 #### GET /pdf-templates PDF layouts Which layout each document type prints with (`uses: original` = the classic PDF) and the available layouts with thumbnails. Contact-specific overrides are listed for people who manage finance or company settings. Staff only. Responses: 200, 401 #### GET /pdf-templates/resolve Which PDF layout applies For a document `type` (invoice, estimate, proposal, credit_note, order, payment, purchase_order, purchase_bill, vendor_credit, payslip) and optionally a `contact_id` with its own override. Parameters: type (query), contact_id (query) Responses: 200, 401 ### Dashboard Endpoints for dashboard. #### GET /dashboard List Dashboard Returns a paginated list of Dashboard in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /dashboard/me My Dashboard (Dashboard) Performs the "My Dashboard" action on a dashboard. Responses: 200, 401, 403, 422 ### Reports The Reports hub: list the reports you may open, run one for a date range with filters (KPIs, chart series, table), export CSV/Excel/PDF, favourite or pin. #### GET /reports Reports I can open Each report decides access per person: `all` (company-wide) or `own` (only your own data). Groups: people, time, work, sales, finance. Responses: 200, 401 #### POST /reports/preferences Favourite or pin a report Toggles. Pins (admins only, max 6) show as KPI widgets on the dashboard. Body: report*, type* Responses: 200, 403 #### GET /reports/{report_key} Run a report KPIs (with the previous period), chart series and the table, exactly as the Reports page shows them. Range: `preset` or `start` + `end` (YYYY-MM-DD, max ~3 years); filters: `f[]` from `filters`. `kpis=1` returns only the KPI cards; `fresh=1` skips the 10-minute cache. A report you may not open answers 404. Parameters: report_key (path, required), preset (query), start (query), end (query), kpis (query), fresh (query) Responses: 200, 404, 422 #### GET /reports/{report_key}/export/{format} Export a report CSV, Excel or PDF of the report table, same parameters as running it. **Permission:** the export right (as the Export button). Parameters: report_key (path, required), format (path, required) Responses: 200, 403, 404 ### Integrations Connected apps: status and last sync of every integration, the sync log, test connection, sync now, the email provider (BusinessHRM / SMTP / Resend / Mailgun), the SMS provider (BusinessHRM / your Twilio) and Twilio number search, buy (with explicit confirm) and select. Secrets are write-only. #### GET /integrations All integrations with status Every app in Settings → Connected apps that your company's plan includes: `status` (connected, platform = BusinessHRM default, warning, needs_keys, error = last sync failed, off, info = guide for a government service), last sync time/result/error, country tags and which `sync_actions` exist. **Permission:** admin or manage_company_setting = all. Responses: 200, 403 #### GET /integrations/email/provider Email sending provider Which provider sends your company's email: `system` (BusinessHRM, default), `smtp`, `resend` or `mailgun`, the From address, health (paused = failing provider, mail moved to BusinessHRM email). Passwords and API keys are never returned (`has_password`, `has_api_key`). Responses: 200, 401 #### PUT /integrations/email/provider Change the email provider Switch provider. `system` needs nothing else. smtp: smtp_host, smtp_port, smtp_username, smtp_password (blank keeps saved), smtp_encryption. resend: api_key (first time). mailgun: api_key, api_domain, api_region us|eu. Then send a test from the app — a failing provider is paused automatically and BusinessHRM email takes over. **Plan:** own providers need the plan's "own email and SMS provider" option. Body: provider*, from_name, from_email, smtp_host, smtp_port, smtp_username, smtp_password, smtp_encryption, api_key, api_domain, api_region Responses: 200, 403 #### GET /integrations/sms/provider SMS provider `system` (BusinessHRM SMS, default) or `twilio` (your own account). The auth token is never returned. Responses: 200, 401 #### PUT /integrations/sms/provider Change the SMS provider provider system | twilio; for twilio: account_sid, auth_token (blank keeps saved), from_number, whatsapp_from_number. Body: provider*, account_sid, auth_token, from_number, whatsapp_from_number Responses: 200, 401 #### POST /integrations/sms/twilio/search Search Twilio numbers for sale Numbers your Twilio account can buy, with the monthly price for the type in `meta.monthly_price`. Uses the saved credentials unless account_sid/auth_token are sent. Body: country*, type, area_code, contains, sms, mms, voice Responses: 200, 422 #### GET /integrations/sms/twilio/numbers Your Twilio numbers Numbers on the account and whether their incoming-SMS webhook points at BusinessHRM. Responses: 200, 401 #### POST /integrations/sms/twilio/buy Buy a Twilio number Buys the number on your Twilio account (Twilio bills you monthly) and makes it the sending number. **`confirm` must be true** — show the price from search to the person first. Body: number*, confirm* Responses: 200, 422 #### POST /integrations/sms/twilio/select Choose the sending number Makes one of your Twilio numbers the SMS sender (and optionally the WhatsApp sender) and points its incoming-SMS webhook at BusinessHRM. Body: number*, whatsapp Responses: 200, 422 #### GET /integrations/{app}/logs Sync log of one app Recent tests and syncs (newest first) with status queued | running | success | partial | failed, counts and the error. Secrets are never included. Query `limit` (1–100, default 30). Parameters: app (path, required), limit (query) Responses: 200, 404 #### POST /integrations/{app}/test Test connection Calls the service now with the saved settings and returns a precise result (e.g. "Xero rejected the access token — reconnect"). Logged in the sync log; a failure turns the app red until a later run passes. Parameters: app (path, required) Responses: 200, 422 #### POST /integrations/{app}/sync Sync now (queued) Queues one sync in the background (retries after 1, 5 and 15 minutes when the failure is temporary). Poll `/integrations/{app}/logs` for the result. `action` = one of the app's `sync_actions` (default the first). Parameters: app (path, required) Body: action Responses: 200, 422 ### AI & key access What the API key or AI connection on this request may do: module levels, read-only, sensitive data, confirmation rules, rate limit and scopes. #### GET /ai/access What this key or AI connection may do Returns the effective access of the token on this request: every module with its level (`none`, `read`, `write`), `read_only`, which sensitive data categories are shared (`salary`, `bank`, `national_id`, `leave_reason`; fields of the others are removed from every answer), whether destructive AI actions need a confirmation, the AI rate limit and the key's scopes. `governed: false` means a person signed in to the apps (only their role applies). Call it to explain or avoid a 403. Always allowed. Responses: 200, 401 ### Sign in (apps) Endpoints for sign in (apps). #### GET /auth/providers Sign-in methods offered by this server Public, no token. Tells an app which sign-in buttons to show. `google.enabled` is true when Google sign-in is switched on in Super Admin → Settings → Social login and a Google **web** OAuth client id is saved; `google.server_client_id` is that web client id, which the app passes to Google Sign-In as *serverClientId* so the ID token is issued for this server. No secret is ever returned. A server without this endpoint answers 404 - treat that as "password only". Responses: 200, 429 #### POST /auth/google Sign in with a Google ID token Public, no token. Send the Google **ID token** the app received from Google Sign-In (requested with `serverClientId` = `google.server_client_id` from `GET /auth/providers`). The server verifies it with Google (signature, issuer, audience = the web client id, expiry, verified e-mail) and signs in the BusinessHRM account that uses the same e-mail. The answer is **identical to `POST /auth/login`**: an identity token plus `user_companies`; continue with `POST /logintoworkspace` exactly as after a password sign-in. Only existing accounts can sign in - there is no sign-up here (an unknown e-mail answers 404). A deactivated account or a workspace with login disabled is refused the same way as with a password. Limited to 10 attempts a minute per IP address. Body: id_token* Responses: 200, 401, 403, 404, 422, 429 ## Other ### Advanced Endpoints for advanced. #### GET /dashboard/advanced/tabs Tabs (Advanced) Performs the "Tabs" action on a advanced. Responses: 200, 401, 403, 422 #### GET /dashboard/advanced List Advanced Returns a paginated list of Advanced in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 ### Appointments Endpoints for appointments. #### GET /appointment-calendars Calendars (Appointment) Performs the "Calendars" action on a appointment. Responses: 200, 401, 403, 422 #### GET /appointment-types/{id}/slots Slots (Appointment) Performs the "Slots" action on a appointment. Parameters: id (path, required) Responses: 200, 401, 403, 422 #### GET /appointments List Appointments Returns a paginated list of Appointments in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /appointments Create a Appointment Creates a new appointment. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: Responses: 200, 401, 403, 422 #### GET /appointments/{id} Get a Appointment Fetches a single appointment by its id, including its detail fields. Parameters: id (path, required) Responses: 200, 401, 403, 422 #### PUT /appointments/{id} Update a Appointment Updates an existing appointment by its id. Send only the fields you want to change; everything else stays as-is. Parameters: id (path, required) Body: Responses: 200, 401, 403, 422 #### POST /appointments/{id}/cancel Cancel (Appointment) Performs the "Cancel" action on a appointment. Parameters: id (path, required) Responses: 200, 401, 403, 422 #### POST /appointments/{id}/reschedule Reschedule (Appointment) Performs the "Reschedule" action on a appointment. Parameters: id (path, required) Responses: 200, 401, 403, 422 ### Can Approve Endpoints for can approve. #### GET /loan/can-approve List Can Approve Returns a paginated list of Can Approve in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### PUT /loan/{loan}/approve Approve (Can Approve) Performs the "Approve" action on a can approve. Parameters: loan (path, required) Responses: 200, 401, 403, 422 #### PUT /loan/{loan}/reject Reject (Can Approve) Performs the "Reject" action on a can approve. Parameters: loan (path, required) Responses: 200, 401, 403, 422 ### Chat Channel Endpoints for chat channel. #### GET /chat-channel/{channel_id}/messages A channel's posts Newest first, each with author and files. Cursor paging: `before_id` (older than) or `after_id` (newer than), 20 per page unless `limit` is given. For threads, reactions, pins and per-person read state use the `chat/*` endpoints. Parameters: channel_id (path, required), before_id (query), after_id (query), limit (query) Responses: 200, 404 #### POST /chat-channel/{channel_id}/messages Post in a channel Parameters: channel_id (path, required) Body: message* Responses: 200, 404 #### DELETE /chat-channel/{channel_id}/messages/{message_id} Destroy Message (Chat Channel) Performs the "Destroy Message" action on a chat channel. Parameters: channel_id (path, required), message_id (path, required) Responses: 200, 401, 403, 422 #### POST /chat-channel/{channel_id}/messages/{message_id}/files Store File (Chat Channel) Performs the "Store File" action on a chat channel. Parameters: channel_id (path, required), message_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /chat-channel/{channel_id}/messages/{message_id}/files/{file_id} Destroy File (Chat Channel) Performs the "Destroy File" action on a chat channel. Parameters: channel_id (path, required), message_id (path, required), file_id (path, required) Responses: 200, 401, 403, 422 #### POST /chat-channel/{channel_id}/read Mark Seen (Chat Channel) Performs the "Mark Seen" action on a chat channel. Parameters: channel_id (path, required) Responses: 200, 401, 403, 422 #### GET /chat-channel/{channel_id}/members Members (Chat Channel) Performs the "Members" action on a chat channel. Parameters: channel_id (path, required) Responses: 200, 401, 403, 422 #### POST /chat-channel/{channel_id}/members Add Members (Chat Channel) Performs the "Add Members" action on a chat channel. Parameters: channel_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /chat-channel/{channel_id}/members/{user_id} Remove Member (Chat Channel) Performs the "Remove Member" action on a chat channel. Parameters: channel_id (path, required), user_id (path, required) Responses: 200, 401, 403, 422 #### POST /chat-channel/{channel_id}/leave Leave (Chat Channel) Performs the "Leave" action on a chat channel. Parameters: channel_id (path, required) Responses: 200, 401, 403, 422 #### GET /chat-channel List Chat Channel Returns a paginated list of Chat Channel in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /chat-channel Create a Chat Channel Creates a new chat channel. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: Responses: 200, 401, 403, 422 #### GET /chat-channel/{channel_id} Get a Chat Channel Fetches a single chat channel by its id, including its detail fields. Parameters: channel_id (path, required) Responses: 200, 401, 403, 422 #### PUT /chat-channel/{channel_id} Update a Chat Channel Updates an existing chat channel by its id. Send only the fields you want to change; everything else stays as-is. Parameters: channel_id (path, required) Body: Responses: 200, 401, 403, 422 #### DELETE /chat-channel/{channel_id} Delete a Chat Channel Permanently deletes a chat channel by its id. This cannot be undone. Parameters: channel_id (path, required) Responses: 200, 401, 403, 422 ### Contact Company Manage contact company. #### GET /contact-company List Contact Company Returns a paginated list of Contact Company in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /contact-company Create a Contact Company Creates a new contact company. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: name*, email, phone, website, address, city, state, country, postal_code Responses: 200, 401, 403, 422 #### GET /contact-company/{contact_company} Get a Contact Company Fetches a single contact company by its id, including its detail fields. Parameters: contact_company (path, required) Responses: 200, 401, 403, 422 ### Contact Tag Manage contact tag. #### GET /contact-tag List Contact Tag Returns a paginated list of Contact Tag in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /contact-tag Create a Contact Tag Creates a new contact tag. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: name*, color Responses: 200, 401, 403, 422 #### GET /contact-tag/{contact_tag} Get a Contact Tag Fetches a single contact tag by its id, including its detail fields. Parameters: contact_tag (path, required) Responses: 200, 401, 403, 422 ### Custom Field Endpoints for custom field. #### GET /custom-field List Custom Field Returns a paginated list of Custom Field in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 ### Deal Agent Endpoints for deal agent. #### GET /deal-pipeline Pipelines (Deal Agent) Performs the "Pipelines" action on a deal agent. Responses: 200, 401, 403, 422 #### GET /deal-agent List Deal Agent Returns a paginated list of Deal Agent in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /deal/{deal_id}/tab/sections Sections (Deal Agent) Performs the "Sections" action on a deal agent. Parameters: deal_id (path, required) Responses: 200, 401, 403, 422 #### GET /deal/{deal_id}/tab/overview Overview (Deal Agent) Performs the "Overview" action on a deal agent. Parameters: deal_id (path, required) Responses: 200, 401, 403, 422 #### GET /deal/{deal_id}/tab/history History (Deal Agent) Performs the "History" action on a deal agent. Parameters: deal_id (path, required) Responses: 200, 401, 403, 422 #### GET /deal/{deal_id}/tab/proposals Proposals (Deal Agent) Performs the "Proposals" action on a deal agent. Parameters: deal_id (path, required) Responses: 200, 401, 403, 422 #### PUT /deal/{deal_id}/tab/stage Change Stage (Deal Agent) Performs the "Change Stage" action on a deal agent. Parameters: deal_id (path, required) Responses: 200, 401, 403, 422 #### GET /deal/{deal_id}/tab/notes Notes (Deal Agent) Performs the "Notes" action on a deal agent. Parameters: deal_id (path, required) Responses: 200, 401, 403, 422 #### POST /deal/{deal_id}/tab/notes Store Note (Deal Agent) Performs the "Store Note" action on a deal agent. Parameters: deal_id (path, required) Responses: 200, 401, 403, 422 #### PUT /deal/{deal_id}/tab/notes/{note_id} Update Note (Deal Agent) Performs the "Update Note" action on a deal agent. Parameters: deal_id (path, required), note_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /deal/{deal_id}/tab/notes/{note_id} Destroy Note (Deal Agent) Performs the "Destroy Note" action on a deal agent. Parameters: deal_id (path, required), note_id (path, required) Responses: 200, 401, 403, 422 #### GET /deal/{deal_id}/tab/follow-ups Follow Ups (Deal Agent) Performs the "Follow Ups" action on a deal agent. Parameters: deal_id (path, required) Responses: 200, 401, 403, 422 #### POST /deal/{deal_id}/tab/follow-ups Store Follow Up (Deal Agent) Performs the "Store Follow Up" action on a deal agent. Parameters: deal_id (path, required) Responses: 200, 401, 403, 422 #### PUT /deal/{deal_id}/tab/follow-ups/{follow_up_id} Update Follow Up (Deal Agent) Performs the "Update Follow Up" action on a deal agent. Parameters: deal_id (path, required), follow_up_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /deal/{deal_id}/tab/follow-ups/{follow_up_id} Destroy Follow Up (Deal Agent) Performs the "Destroy Follow Up" action on a deal agent. Parameters: deal_id (path, required), follow_up_id (path, required) Responses: 200, 401, 403, 422 #### GET /deal/{deal_id}/tab/files Files (Deal Agent) Performs the "Files" action on a deal agent. Parameters: deal_id (path, required) Responses: 200, 401, 403, 422 #### POST /deal/{deal_id}/tab/files Store File (Deal Agent) Performs the "Store File" action on a deal agent. Parameters: deal_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /deal/{deal_id}/tab/files/{file_id} Destroy File (Deal Agent) Performs the "Destroy File" action on a deal agent. Parameters: deal_id (path, required), file_id (path, required) Responses: 200, 401, 403, 422 ### Employee Invite Endpoints for employee invite. #### GET /employee/{employee_id}/tab/sections Sections (Employee Invite) Performs the "Sections" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### GET /employee/{employee_id}/tab/bank Bank (Employee Invite) Performs the "Bank" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### POST /employee/{employee_id}/tab/bank Store Bank (Employee Invite) Performs the "Store Bank" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### GET /employee/{employee_id}/tab/immigration Immigration (Employee Invite) Performs the "Immigration" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### GET /employee/{employee_id}/tab/documents Documents (Employee Invite) Performs the "Documents" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### POST /employee/{employee_id}/tab/documents Store Document (Employee Invite) Performs the "Store Document" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### GET /employee/{employee_id}/tab/permissions Permissions (Employee Invite) Performs the "Permissions" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### GET /employee/{employee_id}/tab/leave-quota Leave Quota (Employee Invite) Performs the "Leave Quota" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### GET /employee/{employee_id}/tab/loan-access Loan Access (Employee Invite) Performs the "Loan Access" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### GET /employee/{employee_id}/tab/emergency-contacts Emergency Contacts (Employee Invite) Performs the "Emergency Contacts" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### POST /employee/{employee_id}/tab/emergency-contacts Store Emergency Contact (Employee Invite) Performs the "Store Emergency Contact" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### GET /employee/{employee_id}/tab/increment-promotions Increment Promotions (Employee Invite) Performs the "Increment Promotions" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### GET /employee/{employee_id}/tab/appreciations Appreciations (Employee Invite) Performs the "Appreciations" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### GET /employee/{employee_id}/tab/shift-roster Shift Roster (Employee Invite) Performs the "Shift Roster" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### GET /employee/{employee_id}/tab/activity Activity (Employee Invite) Performs the "Activity" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### PUT /employee/{employee_id}/tab/bank/{bank_id} Update Bank (Employee Invite) Performs the "Update Bank" action on a employee invite. Parameters: employee_id (path, required), bank_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /employee/{employee_id}/tab/bank/{bank_id} Destroy Bank (Employee Invite) Performs the "Destroy Bank" action on a employee invite. Parameters: employee_id (path, required), bank_id (path, required) Responses: 200, 401, 403, 422 #### PUT /employee/{employee_id}/tab/emergency-contacts/{contact_id} Update Emergency Contact (Employee Invite) Performs the "Update Emergency Contact" action on a employee invite. Parameters: employee_id (path, required), contact_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /employee/{employee_id}/tab/emergency-contacts/{contact_id} Destroy Emergency Contact (Employee Invite) Performs the "Destroy Emergency Contact" action on a employee invite. Parameters: employee_id (path, required), contact_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /employee/{employee_id}/tab/documents/{document_id} Destroy Document (Employee Invite) Performs the "Destroy Document" action on a employee invite. Parameters: employee_id (path, required), document_id (path, required) Responses: 200, 401, 403, 422 #### POST /employee/{employee_id}/tab/passport Store Passport (Employee Invite) Performs the "Store Passport" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### PUT /employee/{employee_id}/tab/passport/{passport_id} Update Passport (Employee Invite) Performs the "Update Passport" action on a employee invite. Parameters: employee_id (path, required), passport_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /employee/{employee_id}/tab/passport/{passport_id} Destroy Passport (Employee Invite) Performs the "Destroy Passport" action on a employee invite. Parameters: employee_id (path, required), passport_id (path, required) Responses: 200, 401, 403, 422 #### POST /employee/{employee_id}/tab/visa Store Visa (Employee Invite) Performs the "Store Visa" action on a employee invite. Parameters: employee_id (path, required) Responses: 200, 401, 403, 422 #### PUT /employee/{employee_id}/tab/visa/{visa_id} Update Visa (Employee Invite) Performs the "Update Visa" action on a employee invite. Parameters: employee_id (path, required), visa_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /employee/{employee_id}/tab/visa/{visa_id} Destroy Visa (Employee Invite) Performs the "Destroy Visa" action on a employee invite. Parameters: employee_id (path, required), visa_id (path, required) Responses: 200, 401, 403, 422 #### PUT /employee/{employee_id}/tab/leave-quota/{quota_id} Update Leave Quota (Employee Invite) Performs the "Update Leave Quota" action on a employee invite. Parameters: employee_id (path, required), quota_id (path, required) Responses: 200, 401, 403, 422 #### POST /employee-invite Create a Employee Invite Creates a new employee invite. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: Responses: 200, 401, 403, 422 ### Files Endpoints for files. #### GET /file/{name} Get a File Fetches a single file by its id, including its detail fields. Parameters: name (path, required) Responses: 200, 401, 403, 422 #### POST /file Create a File Creates a new file. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Body: user_id*, task_id*, filename*, description, google_url, hashname, size, dropbox_link, external_link, external_link_name Responses: 200, 401, 403, 422 #### GET /task/{task_id}/file List Files Returns a paginated list of Files in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: task_id (path, required), limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /task/{task_id}/file Create a File Creates a new file. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Parameters: task_id (path, required) Body: user_id*, task_id*, filename*, description, google_url, hashname, size, dropbox_link, external_link, external_link_name Responses: 200, 401, 403, 422 #### PUT /task/{task_id}/file/{file} Update a File Updates an existing file by its id. Send only the fields you want to change; everything else stays as-is. Parameters: task_id (path, required), file (path, required) Body: user_id, task_id, filename, description, google_url, hashname, size, dropbox_link, external_link, external_link_name Responses: 200, 401, 403, 422 #### DELETE /task/{task_id}/file/{file} Delete a File Permanently deletes a file by its id. This cannot be undone. Parameters: task_id (path, required), file (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/member/{id} Destroy Member (File) Performs the "Destroy Member" action on a file. Parameters: project_id (path, required), id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/members Members (File) Performs the "Members" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/members-add Store Member (File) Performs the "Store Member" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/members/{member_id} Update Member (File) Performs the "Update Member" action on a file. Parameters: project_id (path, required), member_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/files List Files Returns a paginated list of Files in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: project_id (path, required), limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/files Create a File Creates a new file. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Parameters: project_id (path, required) Body: Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/files/{file_id} Delete a File Permanently deletes a file by its id. This cannot be undone. Parameters: project_id (path, required), file_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/notes Notes (File) Performs the "Notes" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/notes Store Note (File) Performs the "Store Note" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/notes/{note_id} Update Note (File) Performs the "Update Note" action on a file. Parameters: project_id (path, required), note_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/notes/{note_id} Destroy Note (File) Performs the "Destroy Note" action on a file. Parameters: project_id (path, required), note_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/discussions Discussions (File) Performs the "Discussions" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/discussions Store Discussion (File) Performs the "Store Discussion" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/discussion-categories Discussion Categories (File) Performs the "Discussion Categories" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/discussions/{discussion_id} Update Discussion (File) Performs the "Update Discussion" action on a file. Parameters: project_id (path, required), discussion_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/discussions/{discussion_id} Destroy Discussion (File) Performs the "Destroy Discussion" action on a file. Parameters: project_id (path, required), discussion_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/discussions/{discussion_id}/replies Discussion Replies (File) Performs the "Discussion Replies" action on a file. Parameters: project_id (path, required), discussion_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/discussions/{discussion_id}/replies Store Discussion Reply (File) Performs the "Store Discussion Reply" action on a file. Parameters: project_id (path, required), discussion_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/discussions/{discussion_id}/replies/{reply_id} Update Discussion Reply (File) Performs the "Update Discussion Reply" action on a file. Parameters: project_id (path, required), discussion_id (path, required), reply_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/discussions/{discussion_id}/replies/{reply_id} Destroy Discussion Reply (File) Performs the "Destroy Discussion Reply" action on a file. Parameters: project_id (path, required), discussion_id (path, required), reply_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/milestones Milestones (File) Performs the "Milestones" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/milestones Store Milestone (File) Performs the "Store Milestone" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/milestones/{milestone_id} Update Milestone (File) Performs the "Update Milestone" action on a file. Parameters: project_id (path, required), milestone_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/milestones/{milestone_id} Destroy Milestone (File) Performs the "Destroy Milestone" action on a file. Parameters: project_id (path, required), milestone_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/timelogs Timelogs (File) Performs the "Timelogs" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/expenses Expenses (File) Performs the "Expenses" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/invoices Invoices (File) Performs the "Invoices" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/estimates Estimates (File) Performs the "Estimates" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/payments Payments (File) Performs the "Payments" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/tickets Tickets (File) Performs the "Tickets" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/rating Rating (File) Performs the "Rating" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/activity Activity (File) Performs the "Activity" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/credentials Credentials (File) Performs the "Credentials" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### POST /project/{project_id}/credentials Store Credential (File) Performs the "Store Credential" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/credential-categories Credential Categories (File) Performs the "Credential Categories" action on a file. Parameters: project_id (path, required) Responses: 200, 401, 403, 422 #### GET /project/{project_id}/credentials/{credential_id}/reveal Reveal Credential (File) Performs the "Reveal Credential" action on a file. Parameters: project_id (path, required), credential_id (path, required) Responses: 200, 401, 403, 422 #### PUT /project/{project_id}/credentials/{credential_id} Update Credential (File) Performs the "Update Credential" action on a file. Parameters: project_id (path, required), credential_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /project/{project_id}/credentials/{credential_id} Destroy Credential (File) Performs the "Destroy Credential" action on a file. Parameters: project_id (path, required), credential_id (path, required) Responses: 200, 401, 403, 422 ### Forms Endpoints for forms. #### GET /forms List Forms Returns a paginated list of Forms in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /forms/{id} Get a Form Fetches a single form by its id, including its detail fields. Parameters: id (path, required) Responses: 200, 401, 403, 422 #### GET /forms/{id}/submissions Submissions (Form) Performs the "Submissions" action on a form. Parameters: id (path, required) Responses: 200, 401, 403, 422 #### GET /form-submissions/{id} Submission (Form) Performs the "Submission" action on a form. Parameters: id (path, required) Responses: 200, 401, 403, 422 ### History Endpoints for history. #### GET /task/{task_id}/history List History Returns a paginated list of History in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: task_id (path, required), limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /task/{task_id}/activity History (History) Performs the "History" action on a history. Parameters: task_id (path, required) Responses: 200, 401, 403, 422 ### Notes Endpoints for notes. #### GET /contact/{contact_id}/contacts Contacts (Note) Performs the "Contacts" action on a note. Parameters: contact_id (path, required) Responses: 200, 401, 403, 422 #### GET /contact/{contact_id}/documents Documents (Note) Performs the "Documents" action on a note. Parameters: contact_id (path, required) Responses: 200, 401, 403, 422 #### GET /contact/{contact_id}/notes List Notes Returns a paginated list of Notes in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: contact_id (path, required), limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /contact/{contact_id}/credit-notes Credit Notes (Note) Performs the "Credit Notes" action on a note. Parameters: contact_id (path, required) Responses: 200, 401, 403, 422 #### GET /contact/{contact_id}/payments Payments (Note) Performs the "Payments" action on a note. Parameters: contact_id (path, required) Responses: 200, 401, 403, 422 #### GET /contact/{contact_id}/orders Orders (Note) Performs the "Orders" action on a note. Parameters: contact_id (path, required) Responses: 200, 401, 403, 422 #### GET /contact/{contact_id}/events Events (Note) Performs the "Events" action on a note. Parameters: contact_id (path, required) Responses: 200, 401, 403, 422 #### GET /contact/{contact_id}/tab/sections Sections (Note) Performs the "Sections" action on a note. Parameters: contact_id (path, required) Responses: 200, 401, 403, 422 #### POST /contact/{contact_id}/tab/notes Create a Note Creates a new note. Send the body fields below — required ones are marked; optional ones fall back to their defaults. Parameters: contact_id (path, required) Body: Responses: 200, 401, 403, 422 #### PUT /contact/{contact_id}/tab/notes/{note_id} Update a Note Updates an existing note by its id. Send only the fields you want to change; everything else stays as-is. Parameters: contact_id (path, required), note_id (path, required) Body: Responses: 200, 401, 403, 422 #### DELETE /contact/{contact_id}/tab/notes/{note_id} Delete a Note Permanently deletes a note by its id. This cannot be undone. Parameters: contact_id (path, required), note_id (path, required) Responses: 200, 401, 403, 422 #### POST /contact/{contact_id}/tab/contacts Store Contact (Note) Performs the "Store Contact" action on a note. Parameters: contact_id (path, required) Responses: 200, 401, 403, 422 #### PUT /contact/{contact_id}/tab/contacts/{row_id} Update Contact (Note) Performs the "Update Contact" action on a note. Parameters: contact_id (path, required), row_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /contact/{contact_id}/tab/contacts/{row_id} Destroy Contact (Note) Performs the "Destroy Contact" action on a note. Parameters: contact_id (path, required), row_id (path, required) Responses: 200, 401, 403, 422 #### DELETE /contact/{contact_id}/tab/documents/{doc_id} Destroy Document (Note) Performs the "Destroy Document" action on a note. Parameters: contact_id (path, required), doc_id (path, required) Responses: 200, 401, 403, 422 ### Private Endpoints for private. #### GET /dashboard/private List Private Returns a paginated list of Private in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 ### Profile Endpoints for profile. #### GET /profile/countries Country List (Profile) Performs the "Country List" action on a profile. Responses: 200, 401, 403, 422 #### POST /profile/password Change Password (Profile) Performs the "Change Password" action on a profile. Responses: 200, 401, 403, 422 #### POST /profile/avatar Upload Avatar (Profile) Performs the "Upload Avatar" action on a profile. Responses: 200, 401, 403, 422 #### DELETE /profile/avatar Remove Avatar (Profile) Performs the "Remove Avatar" action on a profile. Responses: 200, 401, 403, 422 #### GET /profile List Profile Returns a paginated list of Profile in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### PUT /profile Update Profile (Profile) Performs the "Update Profile" action on a profile. Responses: 200, 401, 403, 422 ### Project Label Manage project label. #### GET /project-label List Project Label Returns a paginated list of Project Label in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /project-label/{project_label} Get a Project Label Fetches a single project label by its id, including its detail fields. Parameters: project_label (path, required) Responses: 200, 401, 403, 422 ### Project Status Manage project status. #### GET /project-status List Project Status Returns a paginated list of Project Status in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### GET /project-status/{project_status} Get a Project Status Fetches a single project status by its id, including its detail fields. Parameters: project_status (path, required) Responses: 200, 401, 403, 422 ### Push Endpoints for push. #### GET /notification-settings/push List Push Returns a paginated list of Push in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 #### PUT /notification-settings/push Save (Push) Performs the "Save" action on a push. Responses: 200, 401, 403, 422 #### POST /notification-settings/push/test Test (Push) Performs the "Test" action on a push. Responses: 200, 401, 403, 422 ### Separation & End of Service Leavers: separation records (type, reason, notice, last working day, rehire, exit interview) and the statutory end-of-service amounts from the legal rules (PK gratuity, UAE gratuity, Saudi EOS award, UK redundancy + notice, Australian NES notice/redundancy and long service leave, US final pay and state separation notices) with a calculation trail; company end-of-service liability. Confirmed separations feed the Payroll final settlement. Not legal advice. #### GET /separation List separations Separations of the company, newest last working day first. **Permission:** View Employees = All (amounts only with Edit Employees = All or admin). Parameters: status (query), type (query), employee_id (query), from (query), to (query), limit (query), offset (query) Responses: 200, 403 #### POST /separation Record / confirm a separation Creates or updates the employee's open separation. `confirm: true` confirms it: the last working day and notice dates are written to the employee profile (employee_details.last_date, notice_period_start/end_date) and the end-of-service lines feed the employee's **final settlement** (Payroll). Letters are issued from HR Documents. Amounts come from the dated legal rules of the employee's jurisdiction (Country & compliance): PK gratuity (SO 12(6)), UAE gratuity (FDL 33/2021 Art. 51), Saudi end-of-service award (Arts. 84–85), UK statutory redundancy + notice, Australian NES notice/redundancy and state long service leave, US final-pay deadline, PTO payout rule and state separation notices. Every amount has a calculation `trail` and the `rule` it came from (source, effective date, verified). A starting point, not legal advice. **Permission:** Edit Employees = All or admin. Body: employee_id*, type*, reason_category, reason_notes, notice_given_date, last_working_day*, contract_notice_days, notice_served, notice_served_days, rehire_eligible, exit_interview_date, exit_interview_notes, full_award_reason, pro_rata_reason, monthly_basic, monthly_gross, unpaid_leave_days, lsl_taken_weeks, jurisdiction_id, confirm Responses: 200, 403, 404, 422 #### POST /separation/preview Calculate entitlements (no save) Same body as POST /separation; returns the facts used (salary, service, jurisdiction, headcount, policy) and the end-of-service result. Amounts come from the dated legal rules of the employee's jurisdiction (Country & compliance): PK gratuity (SO 12(6)), UAE gratuity (FDL 33/2021 Art. 51), Saudi end-of-service award (Arts. 84–85), UK statutory redundancy + notice, Australian NES notice/redundancy and state long service leave, US final-pay deadline, PTO payout rule and state separation notices. Every amount has a calculation `trail` and the `rule` it came from (source, effective date, verified). A starting point, not legal advice. **Permission:** Edit Employees = All or admin. Body: employee_id*, type*, reason_category, reason_notes, notice_given_date, last_working_day*, contract_notice_days, notice_served, notice_served_days, rehire_eligible, exit_interview_date, exit_interview_notes, full_award_reason, pro_rata_reason, monthly_basic, monthly_gross, unpaid_leave_days, lsl_taken_weeks, jurisdiction_id Responses: 200, 403, 404 #### GET /separation/liability End-of-service liability Accrued gratuity / end-of-service (and Australian long service leave) for every employee employed on `as_of`, as if each left without cause that day, in accrual mode (no minimum-service cut-off). Useful for UAE / Saudi / Pakistan month-end provisions. **Permission:** Edit Employees = All or admin, and Country & compliance in the plan. Parameters: as_of (query), department_id (query), country (query) Responses: 200, 403 #### GET /separation/settings End-of-service policy Company policy used by the calculator: gratuity policy (statutory = paid in the final settlement, fund = paid by a gratuity fund / savings scheme, none = provident fund instead), wage basis, default contract notice, reason categories and separation types. Responses: 200, 403 #### PUT /separation/settings Update the end-of-service policy Send only the fields to change. **Permission:** Edit Employees = All or admin. Body: gratuity_policy, wage_basis, default_notice_days, reason_categories Responses: 200, 403 #### GET /separation/{id} One separation With the end-of-service snapshot saved with it. **Permission:** View Employees = All (amounts only for managers). Parameters: id (path, required) Responses: 200, 403, 404 #### POST /separation/{id}/cancel Cancel a separation Keeps the record as cancelled and clears the exit date it had set. Refused once the final settlement is confirmed. **Permission:** Edit Employees = All or admin. Parameters: id (path, required) Responses: 200, 403, 404 ### Workspace Settings Endpoints for workspace settings. #### GET /workspace-settings List Workspace Settings Returns a paginated list of Workspace Settings in your workspace. Use the query parameters below to page, sort, pick fields and filter the results. Parameters: limit (query), offset (query), fields (query), order (query), filters (query) Responses: 200, 401, 403, 422 ### Payroll Exports Endpoints for payroll exports. #### GET /payroll-export/formats List export formats Every statutory / bank file format: key, country, `bank` or `statutory`, scope (`run`, `month`, `quarter`, `year`), currency, the employee data it reads, the public specification it follows, its settings fields (`write_only` = secret) and the period it would cover for the given run or year/month/quarter. Formats: `generic_bank_csv`, `ae_wps_sif` (UAE WPS SIF), `au_aba` (ABA), `gb_bacs18` (Bacs Std 18), `us_nacha` (NACHA PPD), `pk_bank_bulk` (IBFT / RAAST / bank presets), `sa_wps` (Saudi WPS / Mudad), `pk_fbr_149`, `pk_fbr_annex_c`, `pk_eobi`, `pk_pessi`, `pk_sessi`, `us_w2_data`, `us_941_summary`, `us_new_hire`, `gb_pension_papdis`, `au_stp_data`, `sa_gosi`. **Permission:** Payroll module in the plan, and admin or `edit_payroll` = *all*. Parameters: run_id (query), year (query), month (query), quarter (query) Responses: 200, 403 #### GET /payroll-export/{format}/validate Check an export (problem list) Runs the format's checks without building the file. `problems[]` has `level` (`error` blocks the download, `warning` does not), `message`, `employee`, `user_id`, `field` and `fix_url` (employee bank tab, profile, statutory ids or the export settings). `can_download` is false while the run is not locked or errors remain. Parameters: format (path, required), run_id (query), year (query), month (query), quarter (query) Responses: 200, 403, 404 #### GET /payroll-export/{format}/file Build the export file Builds the file. JSON answer: `filename`, `content_type`, `records`, `total` (control total), `notes`, `warnings` and `content` (the file text). Add `raw=1` to receive the file itself as a download. 422 while the run is not locked or errors remain (see validate). Parameters: format (path, required), run_id (query), year (query), month (query), quarter (query), raw (query) Responses: 200, 403, 404, 422 #### GET /payroll-export/{format}/settings Export settings The company's saved settings for the format, with defaults filled in. Secret fields (employer ids, account numbers) are masked and never returned in full. Parameters: format (path, required) Responses: 200, 403, 404 #### PUT /payroll-export/{format}/settings Update export settings Partial update: send only the fields to change, as top-level keys or inside `settings`. Secret fields are write-only — a blank value keeps the saved one. Values are checked against each field's pattern / options; nothing is saved when one is invalid (422). Stored encrypted. Parameters: format (path, required) Body: settings Responses: 200, 403, 404, 422 ### Safety Incident Actions Endpoints for safety incident actions. #### POST /safety-incident-action/{id}/status Mark a corrective action done / open **Permission:** the action owner, or anyone who may edit the incident. Parameters: id (path, required) Body: status* Responses: 200, 403, 404 ### Grievance Case Categories Endpoints for grievance case categories. #### GET /workplace-case-category List active case categories Configurable in the register settings. `confidential_by_default` sets the default confidentiality of new cases. Responses: 200, 401