Model Context Protocol

Flowtly MCP

Connect any MCP client to Flowtly and work with your data in plain language — read your finances and projects, and take a curated set of actions.

Connect

claude mcp add --transport http flowtly https://mcp.flowtly.eu/mcp

In Claude, ChatGPT, Gemini Enterprise or any other MCP client, add https://mcp.flowtly.eu/mcp as a custom connector and sign in to Flowtly. There is nothing to request and no key to obtain: the client registers itself, and the endpoint URL is the only value most clients ask for.

If yours asks for more, it is a standard OAuth 2.1 public client — registration https://mcp.flowtly.eu/oauth/register, authorization https://mcp.flowtly.eu/oauth/authorize, token https://mcp.flowtly.eu/oauth/token, PKCE S256 required, no client secret. Paste any client_id whole; it is one long unbroken string.

OAuth scopes access to a single organization; every call forwards your own Flowtly token, so the backend enforces your permissions — the connector grants no extra rights.

Tools 287 · 177 read · 110 write · generated live

Finance & accountancy

Invoices, transactions, contracts, suppliers and the ledger primitives.

bankAccounts_create write

Create a bank account (type, name, currency, defaultImportFormat required). Write.

bank string | null
type* string
default boolean
hidden boolean
name* string
number string | null
prefix string | null
currency* string | null
defaultImportFormat* string
bankAccounts_get read

Get one bank account by id — name, currency, bank, and the format its statements are imported in.

id* string | integer
bankAccounts_list read

List the org's bank accounts. Filter by bank, or set hidden to include archived ones. Use it to resolve the bankAccount id that transactions_list filters on.

bank any
hidden any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
bankAccounts_update write

Update a bank account by id. Write.

bank string | null
type string
default boolean
hidden boolean
name string
number string | null
prefix string | null
currency string | null
defaultImportFormat string
id* string | integer
contracts_create write

Create a contract. Write.

name string
direction string
Setting a direction CONFIRMS it. The extraction applier re-marks it immediately afterwards when the party could not be verified; every other caller — including a user correcting the toggle — leaves it confirmed. The value must be `out` (we sell / issue) or `in` (we buy / receive); `unknown` exists but is written only by the extraction applier. NOT `outgoing`/`incoming` — the column is a plain string with no server-side Choice constraint, so a wrong word is STORED, and the contract then matches no direction filter anywhere (flowtly-mcp#113).
counterparty string | null
project string | null
currency string
totalAmount string
Whole number of 1/10000 units — 11 000,00 is "110000000", NOT "11000.00".
area string | null
The area in m2 as AGREED, i.e. before any amendment (#3726).
dateFrom string | null
dateTo string | null
cyclic boolean
Marks the contract as a recurring/ongoing relationship rather than a one-shot deal — e.g. a monthly retainer, ongoing security service, or indefinite tenancy. When true, dateTo is conventionally left null (open-ended) until the contract is actually terminated.
billingInterval string
How often a cyclic contract bills. Null means the cadence has not been stated — it does NOT mean monthly.
billingAnchorDay integer
Day of month the billing period starts, 1-31. Clamped to the last day in shorter months.
billingAnchorMonth integer
Anniversary month for quarterly/annual billing, 1-12. Unused for monthly.
amountPerInterval string
Cost of ONE billing period, as a whole number of 1/10000 units. Distinct from totalAmount, which is the whole contract.
noDocumentExpected boolean
"This contract legitimately has no document" (#3854).
noProjectExpected boolean
"This contract is not attributable to a project" (#3854).
noPaymentScheduleExpected boolean
"No payment schedule will ever be entered for this contract" (#3854).
notes string | null
description string | null
signatureEnvelopeId string | null
signatureStatus string | null
signatureSentAt string | null
signatureCompletedAt string | null
paymentDeadlineDays integer | null
When payment falls due (#3535). Read from prose such as "w terminie 14 dni od dnia doręczenia faktury VAT, z góry" — three separate facts that previously had nowhere to live, so every extracted contract arrived with its terms blank and someone retyped them.
paymentCountedFrom string | null
When the payment deadline starts counting. Invoice DELIVERY and invoice ISSUE are deliberately distinct: they are different dates, and the leases in hand bind the former. Null when the contract is silent.
paymentInAdvance boolean | null
True for "z góry", false for arrears, null when the contract is silent.
contracts_delete write

Delete a contract by id. Write.

id* string | integer
contracts_get read

Get one contract by id — parties, direction, value, cyclic terms and dates.

id* string | integer
contracts_import read

Load MANY contracts in one call, keyed on `name` — the agreement number. Unlike a client or an asset, a contract has NO external-reference column, so the name IS the idempotency key; a batch containing the same name twice is REFUSED WHOLE rather than updating one contract twice, because a duplicate number means the source is wrong. `counterpartyExternalRef` resolves the buyer through the same ref clients_import was given, so the two compose: import the clients, then the contracts, without ever handling a numeric counterparty id — a ref matching no client fails that row rather than creating a partyless contract. `direction` is "out" (we sell) or "in" (we buy); the column has no server-side constraint, so a wrong word is stored and the contract then matches no filter anywhere. PASS dryRun:true FIRST. Max 500 rows. Requires ROLE_CONTRACTS_MANAGER. Write.

contracts* array
The batch, in order. Rows are matched to existing contracts by name — the agreement number. A batch containing the same name twice is REFUSED whole, because a duplicate number means the source is wrong and applying it would update one contract twice.
dryRun boolean
Preview without writing. TRUE reports would-create / would-update / would-skip per row and creates nothing. Defaults to false, which writes.
contracts_list read

List contracts. Filter by direction — the stored values are "out" (we sell / issue) and "in" (we buy / receive), plus "unknown" — a real, filterable state rather than an error. A contract created by uploading a document starts as "unknown" and stays there until extraction or a person settles it, so omit the filter to get all three: "in" and "out" queried separately DO NOT add up to the whole set (flowtly-mcp#130). NOT "outgoing"/"incoming": those match nothing and come back as an empty list rather than an error. Also filters on counterparty, project, cyclic, name or tags. Use it to resolve the contract id that contracts_paymentScheduleLines reads and that deals_win can link a won deal to.

direction any
Filter by contract direction. Accepts one value or an array of them. Stored values are `out` (we sell / issue), `in` (we buy / receive) and `unknown` — a real, filterable state rather than an error: a contract created by uploading a document starts as `unknown` and stays there until extraction or a person settles it. Omit this filter to get all three; `in` and `out` queried separately DO NOT add up to the whole set (flowtly-mcp#130). NOT `outgoing`/`incoming` — those match nothing, and the filter returns an empty list rather than an error (flowtly-mcp#113).
project any
counterparty any
cyclic any
name string
exists.counterparty boolean
tags string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
contracts_paymentScheduleLines read

List a contract's payment schedule — the instalments it is expected to be invoiced or paid in. Pass contractId from contracts_list. This is the plan, not the actuals: compare it against transactions_list to see what has really been paid. Each line's amount is in MINOR UNITS — grosze, not złote: "530000" is 5 300,00, so divide by 100 before reporting a figure to anyone.

contract any
contract.budgetContracts.budget any
contractId* string | integer
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
contracts_update write

Update a contract by id. Write.

name string
direction string
Setting a direction CONFIRMS it. The extraction applier re-marks it immediately afterwards when the party could not be verified; every other caller — including a user correcting the toggle — leaves it confirmed. The value must be `out` (we sell / issue) or `in` (we buy / receive); `unknown` exists but is written only by the extraction applier. NOT `outgoing`/`incoming` — the column is a plain string with no server-side Choice constraint, so a wrong word is STORED, and the contract then matches no direction filter anywhere (flowtly-mcp#113).
counterparty string | null
project string | null
currency string
totalAmount string
Whole number of 1/10000 units — 11 000,00 is "110000000", NOT "11000.00".
area string | null
The area in m2 as AGREED, i.e. before any amendment (#3726).
dateFrom string | null
dateTo string | null
cyclic boolean
Marks the contract as a recurring/ongoing relationship rather than a one-shot deal — e.g. a monthly retainer, ongoing security service, or indefinite tenancy. When true, dateTo is conventionally left null (open-ended) until the contract is actually terminated.
billingInterval string
How often a cyclic contract bills. Null means the cadence has not been stated — it does NOT mean monthly.
billingAnchorDay integer
Day of month the billing period starts, 1-31. Clamped to the last day in shorter months.
billingAnchorMonth integer
Anniversary month for quarterly/annual billing, 1-12. Unused for monthly.
amountPerInterval string
Cost of ONE billing period, as a whole number of 1/10000 units. Distinct from totalAmount, which is the whole contract.
noDocumentExpected boolean
"This contract legitimately has no document" (#3854).
noProjectExpected boolean
"This contract is not attributable to a project" (#3854).
noPaymentScheduleExpected boolean
"No payment schedule will ever be entered for this contract" (#3854).
notes string | null
description string | null
signatureEnvelopeId string | null
signatureStatus string | null
signatureSentAt string | null
signatureCompletedAt string | null
paymentDeadlineDays integer | null
When payment falls due (#3535). Read from prose such as "w terminie 14 dni od dnia doręczenia faktury VAT, z góry" — three separate facts that previously had nowhere to live, so every extracted contract arrived with its terms blank and someone retyped them.
paymentCountedFrom string | null
When the payment deadline starts counting. Invoice DELIVERY and invoice ISSUE are deliberately distinct: they are different dates, and the leases in hand bind the former. Null when the contract is silent.
paymentInAdvance boolean | null
True for "z góry", false for arrears, null when the contract is silent.
id* string | integer
costGroups_create write

Create a cost group / cost center (name + type required). Write.

type* string
name* string
description string | null
icon string | null
accoutancy_reference string | null
costGroups_list read

List cost groups / cost centres — the buckets that costs, suppliers and incoming invoices are filed under. Use it to resolve the costGroup id that suppliers_create requires and that incoming-invoice suggestions propose.

order.name string
order.description string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
costGroups_update write

Update a cost group / cost center's name or type by id. Write.

type string
name string
description string | null
icon string | null
accoutancy_reference string | null
id* string | integer
counterparties_get read

Get one counterparty by id.

id* string | integer
counterparties_list read

List counterparties — every party the org transacts with. The supplier and client flags say which side(s) a counterparty plays, and one record can be both. This is the party on a bank transaction, so it is what incoming invoices and transactions are matched against. Filter by type, supplier, client, cyclic or budgetNeutral.

type any
supplier any
client any
cyclic any
budgetNeutral any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
invoices_create write

Raise a NEW outgoing (sales) invoice — the tool for billing a client for the first time. Do not confuse it with its two neighbours: invoices_import back-files an invoice that was ALREADY issued elsewhere (onboarding history), and incomingInvoices_create files a supplier's COST document. The invoice lands UNSENT: status is derived from the invoice's log rows and a fresh invoice has none, so nothing is rendered, emailed, or submitted to KSeF by this call — treat the result as a draft to review before issuing. `name` is the invoice number and is yours to choose (max 32 chars) — read invoices_list first and follow the org's existing series rather than inventing one, because nothing here allocates the next number for you. Required: name, type ("invoice"), tinType, issueDate, saleDate, dueDate. Pass `client` (IRI from clients_list) and, for a booking that reconciles later, `contract` (IRI from contracts_list) so the invoice shows up under that contract. Line items go in `invoiceRows` — net unit price, quantity and a tax rate per row; the totals are computed from the rows, not passed in. `bankAccount` (from bankAccounts_list) picks the account printed on the document, and `currency` defaults to the org's. Write.

name* string
type* string
parent any
childrenInvoices array
client string
contract string | null
The revenue-side contract (Contract.direction='in') this issued invoice settles against. Nullable — pre-existing invoices have no link, and ad-hoc invoices outside any contract stay unlinked. SET NULL on contract delete so we don't cascade-delete invoice history with the contract.
tinType* string
tinCountry string | null
tin string | null
bankAccount string
currency string | null
conversionRate number | null
issueDate* string
saleDate* string
dueDate* string
paymentMethod string
paidInAdvance boolean
notes string | null
correctionReason string | null
invoiceRows array
internalComment string
invoices_export read

Start a zip export of ISSUED invoices for a period (from/to, both YYYY-MM-DD, inclusive) filtered on SALE DATE — not issue or creation date. Only ISSUED invoices are included; drafts and unsent invoices are excluded, but corrections ARE included. Optional client restricts to one client (id or IRI from clients_list). Max 200 invoices per export — if the period has more, narrow it (e.g. export one month at a time); a period with 0 issued invoices is rejected too. This call only enqueues the job (rendering a month can take minutes) — it does NOT return a download link. Poll invoices_exportStatus with the returned exportId until it reports "ready". Write.

from* string
Start of the export period (inclusive), YYYY-MM-DD. Filters on invoice SALE DATE, not issue/creation date.
to* string
End of the export period (inclusive), YYYY-MM-DD. Filters on invoice SALE DATE, not issue/creation date.
client string | integer
Optional: restrict the export to one client — an id or IRI from clients_list.
invoices_exportStatus read

Poll the status of a zip export started by invoices_export, by exportId. Once status is "ready", the response includes downloadUrl (a short-lived signed link — expires in 1 hour, see expiresAt), filename, and byteSize; the file's bytes are never returned through this tool. If status is "failed", failureReason explains why.

exportId* string
Export job id returned by invoices_export.
invoices_get read

Get one outgoing (sales) invoice by id — client, rows, totals, sale and issue dates, status.

id* string | integer
invoices_import read

File an ALREADY-ISSUED outgoing (sales) invoice into the org — for bringing invoice history in when onboarding. The external invoice number you pass is preserved verbatim, the buyer is resolved by tax id (created if absent), and the invoice lands as issued WITHOUT rendering a PDF, emailing the client, or submitting to KSeF. Importing a number that already exists is a no-op that reports the existing invoice, so a bulk import is safe to re-run — but that guarantee holds for sequential calls only; two genuinely concurrent imports of the same number can both land. Pass expectedGrossTotal (the gross printed on the source document) and the import is rejected if it disagrees with the total computed from the rows. buyer.tin is required — the buyer is never matched by name. Use invoices_create, not this, to raise a genuine new invoice. Write. Pass dryRun:true to PREVIEW without writing — it reports would-create / would-skip and creates no invoice and no client; run a historical backfill dry first and check the counts before running it for real.

name* string
The invoice number from the source document, preserved verbatim — this is NOT generated by Flowtly. Importing a name that already exists on an issued invoice is a no-op that reports the existing invoice (safe to re-run sequentially; two genuinely concurrent imports of the same name can both land).
issueDate* string
Issue date printed on the source document, YYYY-MM-DD.
saleDate* string
Sale date printed on the source document, YYYY-MM-DD.
dueDate* string
Payment due date printed on the source document, YYYY-MM-DD.
buyer* object
The buyer is resolved by tax id, never by name — if no client with this tin exists yet, one is created from name/country.
rows* array
Invoice line items, in order, as printed on the source document.
dryRun boolean
Preview without writing. TRUE reports what WOULD happen — status "would-create" or "would-skip" — and creates no invoice and no client. Run a historical backfill dry FIRST and check the counts. Defaults to false, which writes.
currency string
ISO-4217 currency code, e.g. "PLN" — NOT an IRI. Resolved server-side. Optional, defaults to the org's currency.
paymentMethod string
How the invoice was/will be paid, free text as printed on the source document, e.g. "Bank wire", "Cash". Optional.
bankAccount string | integer
Bank account the payment is due to — an id or IRI from bankAccounts_list. Optional: defaults to the org's default bank account when omitted.
notes string
Free-text note printed on the invoice. Optional.
internalComment string
Internal-only comment, never shown to the buyer. Optional.
externalReference string
Source-system reference, e.g. a KSeF number. Recorded in the import log. Optional.
expectedGrossTotal string
Safety net: the gross total printed on the source document, as a decimal string. If given, the import is rejected when it disagrees with the total computed from rows. Optional but recommended.
invoices_list read

List outgoing (sales) invoices. Filter by client, tags, search, or a saleDate range. Note that saleDate — not issue date and not creation date — is the field invoices_export filters on, so use the same one here when reconciling an export.

client any
name string
id string
tags string
saleDate.before string
saleDate.strictly_before string
saleDate.after string
saleDate.strictly_after string
search string
Free-text search over invoice number and client name.
properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
invoices_update write

Correct an outgoing (sales) invoice by id, before or after issuing. The everyday use is fixing a draft raised by invoices_create — a wrong date, a wrong row, a missing contract link — rather than deleting and re-raising it, which would burn an invoice number. Read invoices_get first: this is a PATCH over a document whose totals are derived from its rows, so replacing `invoiceRows` replaces the whole set, and an invoice that has already been sent will not un-send itself because you edited it. Write.

name string
type string
parent any
childrenInvoices array
contract string | null
The revenue-side contract (Contract.direction='in') this issued invoice settles against. Nullable — pre-existing invoices have no link, and ad-hoc invoices outside any contract stay unlinked. SET NULL on contract delete so we don't cascade-delete invoice history with the contract.
tinType string
tinCountry string | null
tin string | null
bankAccount string
conversionRate number | null
issueDate string
saleDate string
dueDate string
paymentMethod string
paidInAdvance boolean
notes string | null
correctionReason string | null
invoiceRows array
internalComment string
id* string | integer
suppliers_create write

Create a new supplier/contractor record (name, tinType, costGroup required). Write.

costGroup* string
allowDuplicate boolean | null
Not persisted (no ORM\Column): an escape hatch for POST /contractors only, mirroring Client::$allowDuplicate. When true, ContractorCreateProcessor:: refuseIfDuplicate() skips the duplicate-party guard so a genuinely distinct same-name contractor can be created on purpose.
ownerEmployee string | null
tinType* string
tin string | null
tinCountry string | null
cyclic boolean
paymentDeadline integer | null
name* string
description string | null
matchRegex string | null
requireAttachment boolean | null
taxAmount string | null
notIncludeInChart boolean
eInvoicingReferenceNumber string | null
eInvoicingSourceXmlUrl string | null
eInvoicingVisualUrl string | null
contractorGroup string | null
suppliers_list read

List suppliers/contractors — served from /contractors, so "supplier" and "contractor" are the same record. Filter by cyclic for recurring suppliers. Use it to resolve the supplier that a cost, a contract or an incoming invoice is filed against.

cyclic any
order.name string
order.description string
properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
suppliers_update write

Update a supplier/contractor's details (name, tax id, payment terms, etc.) by id. Write.

costGroup string
ownerEmployee string | null
tinType string
tin string | null
tinCountry string | null
cyclic boolean
paymentDeadline integer | null
name string
description string | null
matchRegex string | null
requireAttachment boolean | null
taxAmount string | null
notIncludeInChart boolean
eInvoicingReferenceNumber string | null
eInvoicingSourceXmlUrl string | null
eInvoicingVisualUrl string | null
contractorGroup string | null
id* string | integer
taxGroups_create write

Create a tax group (name + type required). Write.

name* string
isDefault boolean
taxGroups_list read

List tax groups. Use it to resolve the taxGroup id that taxRules_list filters on and that invoice rows carry.

order.name string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
taxGroups_update write

Update a tax group's name or type by id. Write.

name string
isDefault boolean
id* string | integer
transactions_delete write

Delete a bank transaction by id — find it with transactions_list. Reach for this ONLY to undo a bookkeeping mistake that cannot be corrected any other way: a statement imported against the wrong bank account, or rows entered by hand before the real statement arrived and now duplicated by it. A transaction is a record of what the bank did, so deleting one on an imported account makes the ledger disagree with the bank; the backend allows it for ROLE_ADMIN only (a transactions manager can delete solely on cash/manual accounts). BEFORE deleting a suspected duplicate, prove the pair: match the imported row on amount AND invoice number AND counterparty, not amount alone — a payment that arrived after the statement's end date has no counterpart and deleting it destroys the only record of that income. The backend DETACHES rather than deletes what hangs off it: invoice payments survive with their bank line cleared (re-point them with invoiceTransactions_update), attachments and properties are unlinked, while project and employee transaction rows are removed with it. Irreversible, high-impact. Write.

id* string | integer
transactions_importStatement read

Import a bank statement file (e.g. an MT940 .sta file) — pass each file's raw text content verbatim (NOT base64) with a filename. THERE IS NO bankAccount PARAMETER: the backend routes a file by stripping all non-digit characters from your bank accounts' numbers and from the file's bytes, and importing into every account whose digits appear anywhere in the file — so one file can land in several accounts, and a statement for an account that is not set up in Flowtly (or whose number is recorded differently than the bank writes it) imports into none of them, failing with an error that explains exactly why — read that message, it is the only diagnostic this endpoint gives you. On success the response is `{ imported, matching }`: `matching: "in_progress"` means contractor/attachment matching for the new rows is still running after this call returns, so an immediate transactions_list may show rows not yet matched — re-read a little later for the final state. Re-importing the same statement does not create duplicate rows; the importer recognizes transactions it has already seen. Once a statement is in, point an existing no-bank-line payment at one of its rows with invoiceTransactions_update. Write.

files* array
One or more statement files to import in a single call. There is no bankAccount parameter: each file is routed by matching the digits of your bank accounts' numbers against the digits found anywhere in the file's bytes, so one file can import into several accounts, and a file for an account that isn't set up (or whose number is recorded differently than the bank writes it) imports into none of them and the call fails with a message explaining why.
transactions_list read

List bank transactions — the bank feed that incoming invoices are matched against. Filter by bankAccount, counterpartyRole, cost, ignored, hasDetectedProblems, an orderDate/execDate range, or amount.between. Note orderDate and execDate are different: a payment can be ordered in one month and execute in the next.

cost any
counterparty any
bankAccount any
counterpartyRole any
ignored any
hasDetectedProblems boolean
orderDate.before string
orderDate.strictly_before string
orderDate.after string
orderDate.strictly_after string
execDate.before string
execDate.strictly_before string
execDate.after string
execDate.strictly_after string
amount.between string
amount.gt string
amount.gte string
amount.lt string
amount.lte string
properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
search string
Free-text search over transaction description, operation number, note and counterparty name.
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
transactions_suggestions read

Read Flowtly's proposals for one bank transaction — which counterparty, cost group or document it should be filed against. The mirror image of incomingInvoices_suggestions, from the money side.

cost any
counterparty any
bankAccount any
counterpartyRole any
ignored any
hasDetectedProblems boolean
orderDate.before string
orderDate.strictly_before string
orderDate.after string
orderDate.strictly_after string
execDate.before string
execDate.strictly_before string
execDate.after string
execDate.strictly_after string
amount.between string
amount.gt string
amount.gte string
amount.lt string
amount.lte string
properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
search string
Free-text search over transaction description, operation number, note and counterparty name.
refresh boolean
Force a rebuild instead of returning the cached suggestions. Suggestions are cached; this is the only way to regenerate ones that are stale or wrong. The transaction-match rebuild runs synchronously; the OCR/LLM rebuild is dispatched async.
id* string | integer
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.

Projects & tasks

Projects, tasks, boards and comments.

projects_archive read

Archive a project by id — the way to retire a project that cannot be deleted because it has logged time, invoices or budgets hanging off it. Reversible with projects_unarchive. Prefer this over back-dating dateTo, which only makes a project look finished. Write.

id* string | integer
projects_costAllocations read

How costs were split ONTO this project — which transactions and invoice lines were attributed to it, and in what share. Use it to explain a profitability figure rather than just quote it: this is where an unexpected result is traced back to the document that caused it. Requires ROLE_TRANSACTIONS_MANAGER. Read-only.

id* string | integer
projects_create write

Create a project (name + type required; type = fixed-price|time-and-material|non-billable|internal; optional dateFrom/dateTo, client, publicDescription, notes, priceNet). Write.

parent any
client string | null
currency string | null
dateFrom string | null
dateTo string | null
name* string
publicDescription string | null
type* string
clientHash string
notes string | null
description string | null
Free-text description. A third free-text field alongside `publicDescription` (customer-facing, 255) and `notes` (internal): the client asked for "description" and got it as its own field rather than a rename, so nothing that reads either of the other two changes meaning (#3431).
reference string | null
Cross-system integration key — a stable, human-writable handle for joining this project to systems outside Flowtly, because the numeric id is unusable for that (#3431). NOT a display nicety: the UI prefilling it from the name is a convenience on first entry, and every property below is the contract.
icon string | null
priceNet string | null
taxGroup string | null
attributeSet string | null
taskCreatePolicy string
taskVisibility string
projects_folderCounts read

How many projects sit in each project FOLDER, as folderId + total + active. The folderId is a tagDefinition id — resolve names with tagDefinitions_list, and find which groups are folder groups with tagGroups_list (allowedRelations contains "project"). A null folderId is the uncategorized bucket. Counts root projects only, since folders group roots and phases follow their parent. Read-only.

project string
Filter projects by status. Accepts a single value or multiple (CSV, e.g. "active,archived"). Values: active, finished, archived.
employee string
Filter projects assigned to employee
properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
dateFrom.before string
dateFrom.strictly_before string
dateFrom.after string
dateFrom.strictly_after string
dateTo.before string
dateTo.strictly_before string
dateTo.after string
dateTo.strictly_after string
name string
type any
client.name string
budgetProjects.budget.name any
search string
Free-text search over the project's name, client name and reference.
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
projects_get read

Get one project by id — name, type, client, dates, description and price.

id* string | integer
projects_list read

List projects. Filter by type (fixed-price | time-and-material | non-billable | internal), client.name, employee, name, or dateFrom/dateTo ranges. Use it to resolve the project id that tasks, work-time logging, budgets and contracts all take.

project string
Filter projects by status. Accepts a single value or multiple (CSV, e.g. "active,archived"). Values: active, finished, archived.
employee string
Filter projects assigned to employee
properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
dateFrom.before string
dateFrom.strictly_before string
dateFrom.after string
dateFrom.strictly_after string
dateTo.before string
dateTo.strictly_before string
dateTo.after string
dateTo.strictly_after string
name string
type any
client.name string
budgetProjects.budget.name any
search string
Free-text search over the project's name, client name and reference.
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
projects_profitability read

THE PER-PROJECT RESULT — what a project earned against what it cost. This is the number a services or development business is usually trying to see, and the one every other project tool feeds. Pass the project id from projects_list. Requires ROLE_ACCOUNT_MANAGER. Read-only.

id* string | integer
projects_unarchive read

Restore an archived project by id, undoing projects_archive. Write.

id* string | integer
projects_update write

Update a project by id (name, type, dates, description, etc.). Write.

parent any
client string | null
currency string | null
dateFrom string | null
dateTo string | null
name string
publicDescription string | null
type string
clientHash string
notes string | null
description string | null
Free-text description. A third free-text field alongside `publicDescription` (customer-facing, 255) and `notes` (internal): the client asked for "description" and got it as its own field rather than a rename, so nothing that reads either of the other two changes meaning (#3431).
reference string | null
Cross-system integration key — a stable, human-writable handle for joining this project to systems outside Flowtly, because the numeric id is unusable for that (#3431). NOT a display nicety: the UI prefilling it from the name is a convenience on first entry, and every property below is the contract.
color string
icon string | null
priceNet string | null
taxGroup string | null
attributeSet string | null
taskCreatePolicy string
taskVisibility string
id* string | integer
taskComments_create write

Add a comment to a project task (task id + content). Write.

task* string | integer
Task id or IRI to comment on (see tasks_list).
content* string
Comment text.
taskComments_list read

List comments on project tasks, oldest first. Filter by task to read one task's discussion.

task any
order.createdAt string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
taskLists_list read

List task lists — the board columns/sections tasks are filed into. Filter by project. tasks_create takes a list id from here.

project any
name string
order.position string
order.name string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
tasks_create write

Create a project task (title + project required; optional status, list, assignees, dueAt, priority). Write.

title* string
Task title.
project* string | integer
Project id or IRI the task belongs to (see projects_list).
status string | integer
Task status / board column id or IRI (see taskStatuses_list). Move here to change status or mark done.
list string | integer
Task list / board id or IRI (see taskLists_list).
description string
Task description.
priority string
Task priority (e.g. low, medium, high) — backend-validated.
startAt string
Start date-time (ISO 8601).
dueAt string
Due date-time (ISO 8601).
assignees array
Person ids or /people IRIs to assign (see people_list). A bare id is fine — people and employees share it, and the id is expanded to /people/{id}, which is what the API accepts.
tasks_get read

Get one project task by id — title, project, status, list, assignees, dates and recurrence.

id* string | integer
tasks_list read

List project tasks. Filter by project, list, status, assignees, isTemplate, or startAt/dueAt ranges. Recurring tasks expose recurrenceParent and recurrenceRule, so a generated occurrence can be traced back to the rule that produced it. To decide whether a task is DONE, compare its status against taskStatuses_list (isClosed) rather than matching on the status name.

project any
list any
status any
assignees any
subscribers any
recurrenceParent any
recurrenceRule any
isTemplate any
asset any
startAt.before string
startAt.strictly_before string
startAt.after string
startAt.strictly_after string
dueAt.before string
dueAt.strictly_before string
dueAt.after string
dueAt.strictly_after string
completedAt.before string
completedAt.strictly_before string
completedAt.after string
completedAt.strictly_after string
exists.dueAt boolean
exists.assignees boolean
search string
Free-text search over task title and description.
order.position string
order.dueAt string
order.startAt string
order.createdAt string
order.title string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
tasks_update write

Update a project task by id — change status (incl. mark done), assignees, dueAt, title, etc., or MOVE the task to another project by passing `project` (re-parenting; the task list is cleared unless you also name a `list` in the target project, because a list belongs to one project). Write.

id* string | integer
Task id or IRI to update (see tasks_list).
title string
New task title.
project string | integer
Move the task to another project — re-parenting (see projects_list). A task list belongs to one project, so moving clears the task's list unless you also pass `list` naming one in the target project.
status string | integer
Task status / board column id or IRI (see taskStatuses_list). Move here to change status or mark done.
list string | integer
Task list / board id or IRI (see taskLists_list).
description string
Task description.
priority string
Task priority (e.g. low, medium, high) — backend-validated.
startAt string
Start date-time (ISO 8601).
dueAt string
Due date-time (ISO 8601).
assignees array
Person ids or /people IRIs to assign (see people_list). A bare id is fine — people and employees share it, and the id is expanded to /people/{id}, which is what the API accepts.
taskStatuses_list read

List the project task statuses, in board order. isClosed marks the done states and isDefault the status a new task gets. Read this before interpreting a task's status — the names are org-configurable, so "Done" is not a reliable string to match on.

name string
isClosed boolean
isStarted boolean
isDefault boolean
order.position string
order.name string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.

Time

Log work time and read it back.

workTimes_delete write

Delete one logged work-time entry by id. For a duplicate or an entry logged against work that never happened — prefer workTimes_update when the entry is real but wrong, so the hours stay in the record rather than vanishing from it. Logged hours feed project financials and utilisation, so a delete quietly changes reported numbers for a past period. Write.

id* string | integer
workTimes_get read

Get a single work-time entry by id — date, minutes, project, notes and the employee it belongs to.

id* string | integer
workTimes_list read

List work-time (logged hours) entries. Filter by date range (date.after / date.before, YYYY-MM-DD) and optionally by employee or project; page with cursor. Each row carries employeeId/employeeName and projectId/projectName, so this is how you export all logged hours for a period. IMPORTANT: org-wide results require ROLE_WORKING_HOURS_VIEWER. Without it the backend does NOT error — it silently returns only the connected user's own entries, so an "everyone's hours" export can come back containing one person and look perfectly fine. If every row belongs to one employee and you did not filter by employee, the response carries a scopeWarning saying so — surface it to the user rather than presenting the result as org-wide.

employee any
project any
date.before string
date.strictly_before string
date.after string
date.strictly_after string
projectTree string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
workTimes_log write

Log a work-time entry for the connected Flowtly user (date, durationMinutes, project, notes). THE NOTE MUST CLEAR THE SERVER’S THIN-DESCRIPTION CHECK, which a batch backfill hits repeatedly: it needs EITHER about 32 characters (the exact floor is a per-organisation setting, and an org may set it to 0 to switch the check off) OR a “#” ticket reference OR an http(s) link — any one suffices. “Flowtly – Scallier” is refused; “Flowtly – Scallier #FLOW-123” is not. The 422 names propertyPath `description`, which is the server’s name for the field this tool calls `notes`. Write.

date* string
Work day, YYYY-MM-DD.
durationMinutes* integer
Minutes worked (1–1440).
project* string | integer
Project id or (unique) name.
notes* string
What the time was spent on. THE SERVER REFUSES A THIN NOTE, and satisfying ANY ONE of these is enough: (a) at least N characters, N being a per-organisation setting that defaults to 32 — measured after trimming and collapsing runs of spaces, and counted in BYTES, so Polish text reaches the floor a little sooner than its character count suggests; (b) a ticket reference containing '#', e.g. "#FLOW-123"; (c) an http:// or https:// link. An organisation may also set that length to 0, which disables the check entirely. Rejected with 422 “The description should be minimum {N} characters or should contain link or at least identifier of ticket”. NOTE THE NAME: the server calls this field `description`, so the 422's propertyPath reads `description` and matches nothing you sent.
workTimes_update write

Correct one logged work-time entry by id — its date, minutes, project or description. This is how a mis-filed entry is MOVED between projects: workTimes_log only ever creates, so without this a wrong project or a typo in the description is permanent. Read the entry first with workTimes_get. The same thin-description check applies as on workTimes_log: roughly 32 characters — the floor is a per-organisation setting and may be 0, which disables it — OR a “#” ticket reference OR an http(s) link, any one of the three. Write.

employee string
project string
date string
seconds integer
description string | null
projectTask string | null
Optional link to a {@see ProjectTask}. Nullable; existing rows have no link. Set by the Surface 1 picker chip and by Surface 2's close-and-log panel. The validator {@see \App\Validator\WorkTime\WorkTimeProjectTaskConsistencyConstraint} enforces that projectTask.project === workTime.project when this FK is set. On ProjectTask deletion the column is SET NULL — the time logged is preserved, the link is forgotten.
id* string | integer

More

agreements_create write

Create an employment agreement for a person. THIS IS THE STEP THAT MAKES SOMEBODY ACTIVE: people_create only makes the record, and a person with no agreement reports isActive false forever — a bulk import therefore lands 100% inactive until this is run for each of them. TWO THINGS BOTH HAVE TO BE TRUE or they stay inactive with no error: the `type` must be a CALCULABLE one (built-ins "agreement", "annex", "termination" are; "list-of-intent" and "work-experience" are not), and the dateFrom/dateTo window must cover today (pass dateTo null for an ongoing contract rather than a far-future date). `employee` is an IRI — /people/<id> from people_list. Types are org-extendable, so run agreements_list on somebody already active to see the codes this org really uses. Requires ROLE_AGREEMENTS_MANAGER. Write.

employee string | null
IRI of the person this agreement employs, e.g. /people/<id> from people_list. Nullable in the schema, but an agreement with no employee attaches to nobody and moves no one to active - pass it.
type string
The CODE of an agreement_type row, e.g. "agreement". NOT a fixed list: an org can add its own types, which is why this is not an enum. WHICH TYPE MATTERS: only a CALCULABLE type makes the employee count as active. Built-in codes are "agreement", "annex" and "termination" (calculable) and "list-of-intent" and "work-experience" (NOT calculable - an employee whose only agreement is one of these still reports isActive false). Run agreements_list on someone already active to see the codes this org really uses.
variant string
Contract form. Built-in values are "uop" (umowa o prace), "uz" (umowa zlecenie) and "uod" (umowa o dzielo).
amount number
minutesPerWeek integer
jobSize integer
hasFixedWorkingTime boolean
Whether this contract has an agreed weekly working time.
amountType string
Whether `amount` is net or gross: "netto" or "brutto". Defaults to netto.
billingType string
How `amount` is expressed: "per-hour", "per-day", "per-week" or "per-month". Defaults to per-month.
positionName string | null
position string | null
dateFrom string
First day this agreement covers. HALF OF WHAT MAKES THE EMPLOYEE ACTIVE: a dateFrom in the future leaves them inactive until it arrives, which is correct but is the usual surprise after a bulk load.
dateTo string | null
Last day this agreement covers, or null for open-ended. The other half: a dateTo in the past leaves the employee inactive. For an ongoing contract pass null rather than a far-future date.
currency string
cost string | null
hoursPerWeek integer
agreements_get read

Get one employment agreement by id — type, variant, the dateFrom/dateTo window, hoursPerWeek, and the derived `calculable`, `active` and `status`. agreements_list supplies the id. Requires ROLE_AGREEMENTS_MANAGER or ROLE_MEETING_MANAGER. Read-only.

id* string | integer
agreements_list read

List employment agreements — filter by employee (IRI), isActive, type or variant. THE way to answer "why does people_list say this person is inactive": each row carries `calculable` and `active`, and a person is active exactly when they hold one that is both. Also the place to read the agreement `type` codes this org actually uses before calling agreements_create, since an org can add its own. Requires ROLE_AGREEMENTS_MANAGER or ROLE_MEETING_MANAGER. Read-only.

isActive boolean
type any
employee any
variant any
order.type string
order.variant string
order.dateFrom string
order.dateTo string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
agreements_update write

Change an existing employment agreement — the way an agreement is ENDED, because the backend exposes no delete on this resource: set `dateTo` to the last day it covers and the person stops being active from then, with the record and its history intact. That is the correct move for a payroll-adjacent row; there is no way to make one disappear and there should not be. Also the way to correct a wrong `type`, `variant` or `positionName` in place rather than stacking a second agreement on the person — TWO agreements do not cancel out, the calculable one keeps them active, so "add a correct one alongside" silently leaves the wrong one in force. `amount`, `amountType` and `billingType` are accepted but the API never returns them, so you cannot read back what you wrote. Requires ROLE_AGREEMENTS_MANAGER. Write.

type string
The CODE of a row in `agreement_type` — see {@see AgreementType}. Still a plain string and still the same five values it has always held for an org that has not added any; widened 16 -> 63 in Version20260730150000 because an org-entered name slugs into this column and 16 does not hold one (63, not 64, so MySQL can widen it without rebuilding the table — the migration's docblock has the byte arithmetic).
variant string
amount number
minutesPerWeek integer
jobSize integer
hasFixedWorkingTime boolean
Whether this contract has an agreed weekly working time.
amountType string
billingType string
positionName string | null
position string | null
dateFrom string
dateTo string | null
currency string
cost string | null
hoursPerWeek integer
id* string | integer
agreementTypes_create write

Add a contract type to THIS org's list, so an agreement can be recorded against something the five built-ins do not cover — "Umowa zlecenie", "Kontrakt B2B", "Użytkownik funkcyjny". This is configuration, not a code change: the list is a per-tenant table, and a custom type needs no translation entry because its `name` renders verbatim in all seven locales. DO NOT SEND `id`: the code is slugged from the name server-side with diacritics folded ("Użytkownik funkcyjny" becomes "uzytkownik-funkcyjny"), and passing an id is refused with 422 "Update is not allowed for this operation". Post the name and read the assigned code back off the response. `calculable` DEFAULTS TO FALSE AND IS SILENT: it decides who counts as employed — the resourcing bench, holiday accrual, the cost and budget base — so a type meant for people who should NOT accrue leave or occupy an FTE is correct at false, and a type meant for real employment MUST set it true or everyone on it reports inactive with no error. Nothing will tell you which you got. `position` orders the dropdown; `isActive` defaults true. There is no update or delete over MCP on purpose — `agreement.type` stores this row's id as a bare string with no foreign key, so renaming or removing a type orphans every agreement pointing at it. Requires ROLE_AGREEMENTS_MANAGER. Write.

name string | null
The org-entered label. Null for the built-in five, which carry a translation key instead — see the class docblock.
calculable boolean
Does an agreement of this type mean "this person is employed"?
position integer
Dropdown sort key. Ties are legitimate while a user reorders and are broken by the code, so there is deliberately no unique constraint.
isActive boolean
agreementTypes_get read

Get one contract type by id — its name or translationKey, `calculable`, `isActive`, `position` and `builtIn`. The id IS the code, so this reads back a type by the same string an agreement stores in `type`. Use it to confirm a type persisted after agreementTypes_create, and to check `calculable` before putting anyone on it. Requires ROLE_USER. Read-only.

id* string | integer
agreementTypes_list read

List the contract types THIS org can put on an agreement — the values behind `Ludzie > <person> > Umowy > Edytuj umowę`. Read it before agreements_create or agreements_import, because the list is per-tenant: five built-ins ship ("agreement", "annex", "termination", "list-of-intent", "work-experience") and an org can add its own, so a `type` that is valid in one org 422s in another. THE ID IS THE CODE — the `id` on each row is exactly the string `agreements_create` wants in `type`, not a numeric key to look up. `calculable` is the field that decides whether holding this type makes somebody ACTIVE and counts them into the resourcing bench, holiday accrual and the cost base; a non-calculable type leaves them inactive with no error anywhere, which is intended for a type like "list-of-intent" and a silent bug if you picked it by accident. `builtIn` rows carry a translationKey and a null name; custom rows carry a name rendered verbatim and a null translationKey. Requires ROLE_USER. Read-only.

order.position string
order.name string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
allocations_get read

One allocation by id — a single person's booking on a project, with its dates and percentage. allocations_list finds the id; this reads the full record. An allocation with no employee is an OPEN role (unfilled demand), not a booking. Needs the resourcing module. Read-only.

id* string | integer
allocations_list read

List resourcing allocations — date-ranged assignments of a position on a project to an employee (or to nobody yet, an open role). No filters; page with cursor. Each item carries employeeId/employeeName and projectId/projectName already resolved (null employeeId means an open role); positionId is bare — resolve its name via positions_list. source distinguishes sheet-imported rows from ones created directly in Flowtly. Use this to reconcile a resourcing sheet import: read back what landed and compare against what was submitted.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
assetBookings_cancel read

Release an asset — the way an assignment ends, and the closest thing this resource has to a delete (there is no delete operation). Takes the booking id and a `cancelReason` of 3-255 characters; the booking is kept and stamped with `cancelledAt` so the history survives, and the asset becomes free for the next holder. This is the call to make when an employee leaves: assetBookings_list filtered by `employee` finds what they hold, and this releases each one. Requires ROLE_PROPERTY_BOOKINGS_MANAGER. Write.

cancelReason string
id* string | integer
assetBookings_create write

Assign an asset to a person or a project. `property` is the asset IRI (/assets/{id}) and is required. Name the holder ONE of three ways: `relation` with a single IRI (/people/{id} for a person, /projects/{id} for a project), or `relationName` (employee | project) plus `relationId`, or the `employee` / `project` IRI field directly. Exactly one holder must resolve — naming neither is refused with "Employee or Project must be set." and naming both with "Employee and Project cannot be set at the same time." TWO THINGS THAT ARE NOT IN THE SCHEMA AND WILL 422 YOU: the asset must already be reservable (`bookingAllowed: true` — set it with assets_update), a business rule enforced for EVERY caller including a manager, refused with "This asset is not reservable."; and the asset's own `bookingType` (minutes | days | single-days | permanently) is what makes sense of `duration` / `endDate` — a space dedicated to one person indefinitely is `permanently` with a `startDate` and no end. Concurrent bookings on one asset are serialised server-side, so an overlap is refused rather than double-booked. Requires ROLE_PROPERTY_BOOKINGS_MANAGER to book on someone else's behalf. Write.

employee string | null
project string | null
relation string | null
relationName string | null
relationId string | null
property* string | null
startDate string
duration integer | null
endDate string | null
billingAmount string | null
billingCurrency string | null
consumptionSharePercent string | null
assetBookings_get read

Get one asset booking by id — the asset, its holder, the dates, and whether it has been cancelled. Read-only.

id* string | integer
assetBookings_list read

List asset bookings — who or what currently holds each asset, which is the assignment the Assets screen shows and the only place an asset-to-person link actually lives. Each row carries the asset, the holder (`relationName` employee | project plus `relationId`), start/end dates and, once released, `cancelReason` and `cancelledAt`. Filter by `property` to see one asset's history, or by `employee` to see everything one person holds — that second one is what to run before someone leaves. Note `employee` here is the NUMERIC id, not the /people IRI that assetBookings_create takes. Add `exists.cancelledAt: false` to see only what is still held; without it the list includes released bookings too. Read-only.

employee integer
relationName any
relationId any
property any
startDate.before string
startDate.strictly_before string
startDate.after string
startDate.strictly_after string
dateTo string
exists.cancelledAt boolean
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
assetBookings_update write

Update an existing asset booking — its dates, duration, billing amount/currency, or metered-consumption share. `relationName` and `relationId` are required by the payload, so send the holder the booking already has unless you are deliberately moving it. To end an assignment use assetBookings_cancel, not an endDate in the past. Requires ROLE_PROPERTY_BOOKINGS_MANAGER. Write.

relationName string
relationId string
startDate string
duration integer | null
billingAmount string | null
billingCurrency string | null
consumptionSharePercent string | null
This booking's share of the asset's metered consumption, 0–100.
endDate string | null
id* string | integer
assetMeterReadings_get read

Get one meter reading by id — its meter, date and value. Read-only.

id* string | integer
assetMeterReadings_list read

List meter readings — the dated values recorded against an asset meter, the raw data the metered-billing split reads. Each row carries the meter, date and value. Use it to read a meter's history: a value that never moves across periods (a stuck or shared meter) bills zero, and a meter with no recent rows is one nobody is reading. Read-only.

meter any
meter.property any
readingType any
source any
isEstimated boolean
readingAt.before string
readingAt.strictly_before string
readingAt.after string
readingAt.strictly_after string
periodFrom.before string
periodFrom.strictly_before string
periodFrom.after string
periodFrom.strictly_after string
periodTo.before string
periodTo.strictly_before string
periodTo.after string
periodTo.strictly_after string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
assetMeters_get read

Get one asset meter by id — the asset it sits on, utility type, unit and external/QR identifier, with its readings. Read-only.

id* string | integer
assetMeters_list read

List the org's asset meters — the utility/media counters attached to assets (electricity, water, gas, heat). Each carries the asset it sits on, its utility type and unit, and its readings. Filter by `property` (the asset it belongs to) and `utilityType`. Use it to resolve the meter id that readings take, and to spot meters that read zero, are stuck on one value, or sit on a shared/collective meter. Read-only.

property any
utilityType any
readingMode any
externalId any
isActive boolean
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
assetMeters_update write

Update an asset meter — its label, utility type, unit, or active state. Use it to retire a meter from metering (e.g. a utility now billed straight from the invoice) without deleting its reading history. Requires ROLE_PROPERTIES_MANAGER. Write.

property string
name string
utilityType string
unit string
readingMode string
externalId string | null
isActive boolean
note string | null
id* string | integer
assets_create write

Create an asset (name + status + bookingType required; status = in-stock | damaged | sold, bookingType = minutes | days | single-days | permanently). bookingType is required even when the asset is never booked — pass "permanently" for something that is not lent out, and leave bookingAllowed false. Two fields carry the structure: `parent` nests an asset under another (a unit under a building, a monitor under a desk), and `attributeSet` sets the category the Assets list groups by, which is also where custom attributes such as area or floor live. `assetCode` is a UNIQUE cross-system handle — use it to hold the id this asset has in the system of record it was imported from, so a re-import updates rather than duplicates. Requires ROLE_PROPERTIES_MANAGER. Write.

attributeSet string | null
parent any
name* string
bookingAllowed boolean
status* string
bookingType* string
times integer | null
description string
serialNumber string | null
assetCode string | null
boughtAt string | null
warrantyTo string | null
location string | null
assets_get read

Get one asset by id — name, status, category (attributeSet), parent, assetCode, serial number, purchase and warranty dates, location and booking settings. Read-only.

id* string | integer
assets_import read

Load MANY assets in one call, keyed on `assetCode` — the tool for bringing an inventory across from another system, where assets_create would be one round trip per record. Rows reconcile against the org: an unknown assetCode creates, a known one updates in place, a matching row is skipped, so re-running changes nothing and a half-finished run is safe to repeat. `parentAssetCode` nests a row under another BY ITS CODE, resolved against the org and against earlier rows of the same batch; a parent that never resolves fails that row rather than silently orphaning it. THREE FIELDS MAKE THE RECORD READABLE rather than a bare name: `attributeSetName` is the category the UI shows as Typ zasobu and the list groups by, `locationName` is where the thing physically is, and `attributes` is a {name: value} map for area, floor, price and anything else the source carries. All three are resolved BY NAME — the category, the location, the attribute definitions and their bindings are found or created for you, so a caller never handles one of those IRIs, and names are matched case-insensitively so "Mieszkanie" and "mieszkanie " cannot split the list in two. `attributes` needs a category to hang off, and a value that parses as a number creates a number attribute, decided the first time the name appears. An attribute that fails to write does NOT fail its asset. PASS dryRun:true FIRST on a real inventory load — it reports would-create / would-update / would-skip per row and creates nothing at all, categories and locations included. Max 1000 rows. Requires ROLE_PROPERTIES_MANAGER. Write.

assets* array
The batch, in order. Rows are matched to existing assets by assetCode: an unknown code creates, a known one updates in place, and a row identical to what is already stored is skipped. Re-running the same batch therefore changes nothing.
dryRun boolean
Preview without writing. TRUE reports create / update / skip per row and creates nothing — run a first inventory load dry and read the counts before running it for real. Defaults to false, which writes.
assets_list read

List the org's assets — the register of physical things it owns or sells, from laptops and desks to apartments, parking spaces and storage units. Filter by status (in-stock | damaged | sold), attributeSet (the category the Assets list groups by), bookingAllowed, or a partial name or serialNumber; order by name, status, serialNumber, boughtAt or warrantyTo. NOT PAGINATED — the whole set comes back in one response, so a large register is one big payload rather than a first page. Use it to resolve the asset id that asset bookings and asset documents take. Read-only.

status any
bookingType any
attributeSet any
name string
serialNumber string
bookingAllowed boolean
order.name string
order.status string
order.serialNumber string
order.boughtAt string
order.warrantyTo string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
assets_update write

Update an asset by id — name, status, category, parent, assetCode, serial number, dates, location or booking settings. This is how an asset moves from in-stock to sold. Note the status vocabulary is in-stock | damaged | sold and has NO reserved state, so a hold has to be modelled some other way. Requires ROLE_PROPERTIES_MANAGER. Write.

attributeSet string | null
parent any
name string
bookingAllowed boolean
status string
bookingType string
times integer | null
description string
serialNumber string | null
assetCode string | null
boughtAt string | null
warrantyTo string | null
location string | null
id* string | integer
attributeEntityValues_create write

Set an attribute value on one entity (attribute + value required). `relation` IS AN IRI — "/properties/7", not the word "property": the backend resolves it and derives the relation name from the resource class, so passing a bare name throws. (`relationId` takes a plain id and still works, but it is deprecated in favour of the IRI.) The attribute must already be BOUND to that entity's category or the value is stored and never displayed. Requires ROLE_ATTRIBUTES_MANAGER. Write.

attribute* string | null
relation string | null
relationId string | null
value* string | null
dateFrom string | null
dateTo string | null
attributeEntityValues_delete write

Remove an attribute value from an entity. The definition and the binding survive; only this entity's value goes. Requires ROLE_ATTRIBUTES_MANAGER. Write.

id* string | integer
attributeEntityValues_list read

List attribute VALUES — what a specific asset, project, budget or client actually holds for a bound attribute. Each row carries the attribute, the value, and `relationId` naming the entity it belongs to. Read-only.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
attributeEntityValues_update write

Change one attribute value in place, by its id. Use this rather than creating a second value for the same (entity, attribute) pair — nothing enforces uniqueness, so a duplicate is accepted and the UI shows one of them. Requires ROLE_ATTRIBUTES_MANAGER. Write.

value string
dateFrom string | null
dateTo string | null
id* string | integer
attributes_create write

Create an attribute definition (name + type required; type is number | string | date | state | period). THE TYPE IS THE DECISION: it is shared by every entity carrying this attribute, so a field created as `string` cannot later total or sort as a number without every existing value being rewritten. Decide it against the values you actually have, not the first one you see. A definition on its own does nothing — bind it to a category with attributeSetAttributes_create, or it never appears anywhere. Requires ROLE_ATTRIBUTES_MANAGER. Write.

name* string
type* string
shift integer
required boolean
multiple boolean
defaultValue string | null
formatPattern string | null
attributes_get read

Get one attribute definition by id — name, type, whether it is required or multiple, default value and format pattern. Read-only.

id* string | integer
attributes_list read

List attribute DEFINITIONS — the named fields (area, floor, price) that categories bind and assets carry values for. Each has a type: number | string | date | state | period. Read-only.

order.name string
order.type string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
attributes_update write

Update an attribute definition — name, type, required, multiple, default or format. Changing `type` on a definition that already has values is the risky one: existing values are not converted. Requires ROLE_ATTRIBUTES_MANAGER. Write.

name string
shift integer
required boolean
defaultValue string | null
formatPattern string | null
id* string | integer
attributeSetAttributes_create write

Bind an attribute definition to a category (attributeSet + attribute, both IRIs). THIS IS WHAT MAKES AN ATTRIBUTE APPEAR: without the binding a value can be written successfully against an entity and will never show in the UI — a failure with no symptom. Requires ROLE_ATTRIBUTES_MANAGER. Write.

attributeSet* string
attribute* string
required boolean
implicitUnitQuantity boolean
attributeSetAttributes_delete write

Unbind an attribute from a category. The definition and any values survive; they simply stop being shown for that category, which makes this look like data loss when it is not. Requires ROLE_ATTRIBUTES_MANAGER. Write.

id* string | integer
attributeSetAttributes_list read

List the bindings between categories and attribute definitions — which fields appear on which category. Read-only.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
attributeSets_create write

Create a category (name + relationName required; relationName is one of property | project | budget | client, and for an asset category it is the plain string "property" — NOT an IRI). Optional icon from a fixed list (room, parking, building, office, local, desk, monitor and so on) which the UI shows beside the category. LIST FIRST: names are not unique, so a second "Mieszkanie" is accepted and quietly splits the Assets list in two. Requires ROLE_ATTRIBUTES_MANAGER. Write.

name* string
relationName* string
icon string | null
attributeSets_get read

Get one attribute set by id — its name, relationName, icon, and the attributes bound to it. Read-only.

id* string | integer
attributeSets_list read

List the org's attribute sets — the CATEGORIES an asset, project, budget or client is filed under. Filter by relationName: "property" for asset categories (what the UI calls Typ zasobu and the Assets list groups by), plus "project", "budget" and "client". Reach for this before creating one: a category duplicated by spelling or casing silently splits the list it groups, and nothing in the UI explains why. Read-only.

relationName any
order.name string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
attributeSets_update write

Rename a category, change its icon, or move it to another relationName. This is how a category created with a typo gets fixed rather than duplicated. Requires ROLE_ATTRIBUTES_MANAGER. Write.

name string
icon string | null
id* string | integer
banks_create write

Create a bank — the institution a bank account belongs to, not the account itself (that is bankAccounts_create). Write.

name string
swift* string
banks_get read

Get one bank by id — the institution, not an account held with it. Use bankAccounts_get for the account.

id* string | integer
banks_list read

List the banks the org's accounts are held with. Hidden banks are INCLUDED by default — pass hidden=false for the pickers' view, or hidden=true to find the retired ones. Use it to resolve the bank id that bankAccounts_list filters on and that bankAccounts_create needs.

hidden any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
banks_update write

Update a bank by id. This is also how a bank is hidden and un-hidden: set `hidden` true to retire one from the pickers without deleting it, false to bring it back. There is no separate archive tool because the API has no archive action for a bank — the flag is the mechanism. Write.

name string
swift string
hidden boolean
ARCHIVED, NOT DELETED (#4371).
id* string | integer
budgets_employeePnl read

P&L per employee for a budget — what each person's time earned against what they cost. Needs ROLE_BUDGETS_VIEWER. Read-only.

id* string | integer
budgets_get read

Get one budget by id — its period, scope and settings. Needs ROLE_BUDGETS_VIEWER. Read-only.

id* string | integer
budgets_list read

List the org's budgets — the periods against which income and cost are planned and compared. Use it to resolve the budget id that every pnl tool takes. Needs ROLE_BUDGETS_VIEWER. Read-only.

properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
name string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
budgets_pnlByTags read

P&L for a budget, broken down BY TAG — income, costsByTag, costsByProject and netByTag over the budget's periods. The tag axis is what makes this readable for a business whose costs are not naturally per-project: tag the documents, and the split follows. Carries displayPricePerSqm when the org has enabled price-per-sqm and named an area attribute, which is what turns this into a per-square-metre view for a property developer. Needs ROLE_BUDGETS_VIEWER. Read-only.

id* string | integer
budgets_pnlByTagsDrilldown read

The documents behind one cell of budgets_pnlByTags. Reach for it when a tag total looks wrong — it names the transactions making up the number instead of leaving you to guess. Needs ROLE_BUDGETS_VIEWER. Read-only.

id* string | integer
clientContacts_create write

Create a contact person for a client (client, type, name, email required). Write.

client string
type* string
position integer
email* string
name* string
title string | null
phone string | null
clients_create write

Create a new client record (name, country, currency, status, tinType required). Write.

country* string
currency* string
defaultCost string | null
status* string
attributeSet string | null
name* string
dueDays integer
tinType* string
tinCountry string | null
tin string | null
externalPaymentCustomerId string | null
invoiceComment string | null
defaultTaxRate string | null
documentsLanguage string
addressPhoneNumber string | null
addressCity string | null
addressPostCode string | null
addressStreetLine string | null
addressBuildingNumber string | null
addressLocaleNumber string | null
addressCountry string | null
clientContacts array
allowDuplicate boolean | null
Not persisted (no ORM\Column): an escape hatch for POST /clients only, mirroring CounterpartyCreateByTypeInput::allowDuplicate. When true, ClientCreateProcessor::refuseIfDuplicate() skips the duplicate-party guard, matching the human-facing PATCH-and-attach pattern in CounterpartyCreateByTypeProcessor.
vatNumber string | null
clients_get read

Get one client by id — name, country, currency, tax id and status.

id* string | integer
clients_import read

Load MANY clients in one call, keyed on `externalRef` — the tool for bringing a customer or buyer list across from another system, where clients_create would be one round trip per person. Rows reconcile against the org: an unknown externalRef creates, a known one updates in place, a matching row is skipped, so re-running changes nothing. The ref is stored as `externalPaymentCustomerId`, the only external-reference column a client has, and `clients_list` filters on it. DO NOT match clients by name instead — a buyer list is full of shared surnames and joint purchases. Each result carries `counterpartyId`, which is what contracts_import and contracts_create need. Two traps the schema cannot express: a `tin` is REJECTED without a `tinCountry`, and a contact row needs an e-mail, so a phone number alone cannot create one. PASS dryRun:true FIRST on a real onboarding load. Max 500 rows. Requires ROLE_CLIENTS_MANAGER. Write.

clients* array
The batch, in order. Rows are matched to existing clients by externalRef: an unknown ref creates, a known one updates in place, and a row already matching is skipped. Re-running the same batch changes nothing.
dryRun boolean
Preview without writing. TRUE reports would-create / would-update / would-skip per row and creates nothing. Run a first onboarding load dry and read the counts before running it for real. Defaults to false, which writes.
clients_list read

List clients (the org's customers). Filter by status, or by externalPaymentCustomerId to find the client behind a payment-provider id. Use it to resolve the client id that invoices_list, deals_list, projects_list and contracts_list all filter on.

properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
externalPaymentCustomerId any
status any
order.name string
order.status string
order.tin string
order.tinCountry string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
clients_update write

Update a client record by id. Write.

country string
currency string
defaultCost string | null
status string
attributeSet string | null
name string
dueDays integer
tinType string
tinCountry string | null
tin string | null
externalPaymentCustomerId string | null
invoiceComment string | null
defaultTaxRate string | null
documentsLanguage string
addressPhoneNumber string | null
addressCity string | null
addressPostCode string | null
addressStreetLine string | null
addressBuildingNumber string | null
addressLocaleNumber string | null
addressCountry string | null
clientContacts array
vatNumber string | null
id* string | integer
configKeys_catalog read

List every organization config key the backend recognises, with its type and allowed values. This is the catalog of what is configurable — read it before configs_get or configs_update rather than guessing a key name. Permission is enforced per key by the backend, so a key appearing here does not guarantee the connected user may write it.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
configs_get read

Read one organization config value by id, where the id is a key from configKeys_catalog (e.g. organization-logo-url, organization-icon-url).

id* string | integer
configs_update write

Update an organization config value by id (type + name required; permission is enforced per config key by the backend). Write.

type string
name string
value string
Every write path reaches this setter — API Platform's denormalizer for PATCH and POST, `Config::setValue()`, and the providers that seed a default — so a boolean is normalised and validated HERE rather than at any one caller. Fixing `configs_update` alone would have left the next caller with the same footgun.
id* string | integer
contractAttachments_create write

Attach a document to a contract — normally the executed PDF, or an annex (DPA, SLA, price annex) filed alongside it. Pass the bytes as base64 with a fileName and the contract id from contracts_list; `contractId` here is a BARE id, unlike the IRIs contracts_update takes for counterparty and project, though a full /contracts/<id> IRI is accepted and stripped. SIZE LIMIT: the bytes travel as base64 inside this call, so the whole document has to fit in one model response — keep it under roughly 150 KB, and for anything larger use contractAttachments_createUploadTicket instead, which is built for exactly this and has no such ceiling. An executed contract with a signature card is usually well past that (673,617 bytes becomes 898,156 base64 characters, several times what one response can carry), and no error comes back when it does not fit, because the call cannot be emitted at all — the request never reaches the server, so check the file size BEFORE starting rather than discovering it by failing. This is what clears the missing-document problem contracts_get reports, so a contract maintained over the API stops sitting in the app's needs-tidying queue. One signed document can back several contract rows (a deal with both a recurring and a one-shot part is two rows, because `cyclic` is per-record) — call this once per contract id with the same bytes. The backend analyses the document asynchronously: `status` comes back "pending" and analysisSummary lands later, as a SUGGESTION to check rather than a fact to trust. Write.

fileBase64* string
The document's bytes, base64-encoded (normally the signed PDF). Practical ceiling is about 150 KB of file: base64 inflates it by 4/3 and the result has to be emitted inside this call, so a bigger document cannot be sent this way at all — no error arrives, the call simply cannot be written. Use contractAttachments_createUploadTicket for those: it hands back a URL you POST the raw bytes to, so they never pass through the model at all.
fileName* string
Filename for the upload, e.g. Umowa-022-2026.pdf.
contractId* string
Contract to attach it to — the id from contracts_list, or the full /contracts/<id> IRI.
mimeType string
MIME type of the bytes, e.g. application/pdf.
kind string
contract = the agreement itself. annex = a document filed alongside it (DPA, SLA, price annex).
contractAttachments_createUploadTicket read

Mint a short-lived, single-use ticket for attaching a LARGE document to a contract — the executed PDF, or an annex. Use this instead of contractAttachments_create whenever the file is more than a few tens of KB: that tool carries the bytes as base64, which a caller has to emit as text, and a real signed contract (~700 KB, ~900 K base64 characters) is far beyond what fits in one response. Pass the contract id from contracts_list plus a fileName; you get back an uploadUrl and a ready-to-run curl. Then send the file's RAW BYTES to that URL (curl --data-binary @file.pdf) — not base64, not multipart — and the response is the created attachment. The ticket expires in 15 minutes, works once, and can only attach to the one contract it names. This is what clears the missing-document problem contracts_get reports. Write.

contractId* string
Contract the document will be attached to — the id from contracts_list, or the full /contracts/<id> IRI.
fileName* string
Filename to record on the attachment, e.g. Umowa-022-2026.pdf.
mimeType string
MIME type of the file, e.g. application/pdf.
kind string
contract = the agreement itself. annex = a document filed alongside it (DPA, SLA, price annex).
counterpartyBankAccounts_create write

Attach a bank account to a counterparty (counterparty + accountNumber). Write.

counterparty string
accountNumber string
crmNotes_create write

Add a note to a lead or a deal (body + exactly one of lead/deal). Author is the connected user. Write.

body* string
lead string | null
deal string | null
crmNotes_delete write

Delete a CRM note by id. Write.

id* string | integer
crmNotes_get read

Get one CRM note by id.

id* string | integer
crmNotes_list read

List notes written on leads and deals. Filter by lead or deal to read the running commentary on one record.

lead any
deal any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
crmNotes_update write

Update a CRM note’s body by id. Write.

body string
id* string | integer
dealLostReasons_get read

Get one deal lost-reason by id.

id* string | integer
dealLostReasons_list read

List the reasons a deal can be marked lost, in order. deals_lose requires a lostReasonId from here.

order.position string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
deals_create write

Create a deal/opportunity. Required: title, stage (from stages_list), and an ANCHOR — at least one of client or lead. A deal with neither is refused with 422 “A deal must reference a client or a lead.”, so anchor a prospect you have no customer record for to its lead (`/leads/<id>` from leads_list) rather than inventing a client; pass client (`/clients/<id>` from clients_list) once there is one. Setting both is allowed. Optional: amountMinor, currency, expectedCloseDate, owner, contact. Creating straight into a won stage additionally requires client — a lead-only deal cannot be won. Write.

title* string
client string | null
Anchor: a deal must reference a client OR a lead — set at least one; setting both is allowed. IRI of the customer, e.g. /clients/<id> from clients_list. When the prospect has no client record yet, anchor to lead instead. Required on its own only for a won stage: a lead-only deal cannot be won.
contact string | null
stage* string
amountMinor string | null
currency string | null
expectedCloseDate string | null
owner string | null
lead string | null
The other half of the anchor: a deal must reference a client OR a lead — set at least one; setting both is allowed. IRI of the prospect, e.g. /leads/<id> from leads_list. Use it for an opportunity with no customer record yet, then run leads_convert (or set client) before winning the deal.
deals_delete write

Delete a deal by id (soft delete). Write.

id* string | integer
deals_get read

Get one deal by id — title, client, stage, amount, owner, contact, expected and actual close dates.

id* string | integer
deals_list read

List deals/opportunities — the sales pipeline. Filter by status (open / won / lost), stage, owner, client, lead, or by expectedCloseDate / closedAt ranges. Amounts are minor units with an explicit currency; do not assume the org's default.

status any
stage any
owner any
client any
lead any
title string
closedAt.before string
closedAt.strictly_before string
closedAt.after string
closedAt.strictly_after string
expectedCloseDate.before string
expectedCloseDate.strictly_before string
expectedCloseDate.after string
expectedCloseDate.strictly_after string
createdAt.before string
createdAt.strictly_before string
createdAt.after string
createdAt.strictly_after string
order.createdAt string
order.expectedCloseDate string
order.amountMinor string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
deals_lose read

Mark a deal lost — requires lostReasonId (from dealLostReasons_list); optional lostReasonNote. BACKFILLING A HISTORICAL LOSS: pass optional closedAt (ISO-8601) to record the date it ACTUALLY closed, exactly as deals_win does. Omit it and the server stamps now. It may not be in the future (422), and may be earlier than the deal’s createdAt. Write.

lostReasonId* string
REQUIRED. A deal-lost-reason id or IRI, from the picklist at GET /deal-lost-reasons. Both forms are accepted.
lostReasonNote string
Optional free-text note kept alongside the picklist reason -- the detail a fixed list cannot carry.
closedAt string
Optional. When the deal ACTUALLY closed, ISO-8601. Defaults to now; may not be in the future (422).
id* string | integer
deals_reopen read

Reopen a won/lost deal back to open. Write.

id* string | integer
deals_update write

Update a deal by id (title, stage, amountMinor, currency, expectedCloseDate, owner, contact, client, lead). Moving the stage is logged automatically. The anchor rule from deals_create still applies to the result, so you cannot clear the only client or lead a deal has — swap one in first. Moving a deal into a won stage requires client: attach the customer here (or run leads_convert) before winning a lead-only deal. Write.

title string
client string | null
Anchor: the deal must still reference a client OR a lead after this patch, so null is refused when lead is also empty. IRI of the customer, e.g. /clients/<id> from clients_list. Set it before moving the deal into a won stage — a lead-only deal cannot be won.
contact string | null
stage string
amountMinor string | null
currency string | null
expectedCloseDate string | null
owner string | null
lead string | null
The other half of the anchor: the deal must still reference a client OR a lead after this patch, so null is refused when client is also empty. IRI of the prospect, e.g. /leads/<id> from leads_list.
id* string | integer
deals_win read

Mark a deal won — moves it to a won stage and stamps it closed; optional contractId links an existing contract. BACKFILLING A HISTORICAL WIN: pass optional closedAt (ISO-8601, e.g. “2026-05-07” or a full timestamp) to record the date it ACTUALLY closed. Omit it and the server stamps now, which puts an old deal in this month’s “won this month” figure — so set it whenever you are entering a deal that closed before today. It may not be in the future (422), and it MAY be earlier than the deal’s own createdAt: a deal created today and closed in May is the normal shape of a correct backfill, not an error. The deal must ALREADY reference a client: winning a lead-only deal is refused with 422 “Attach a customer before marking this deal Won.”, because there is no customer to bill. Turn the lead into one with leads_convert, or set client with deals_update, then win. Write.

contractId string
Optional id of an existing contract to link to the won deal. Not found -> 422.
closedAt string
Optional. When the deal ACTUALLY closed, ISO-8601 ("2026-05-07", or a full timestamp). Omit it and the server stamps now, which files a historical deal under this month. It may not be in the future (422), and it MAY precede the deal's own createdAt -- that is the normal shape of a backfill, not an error.
id* string | integer
dealStageHistories_get read

Get one deal stage-change record by id.

id* string | integer
dealStageHistories_list read

List a deal's stage transitions, newest first. Filter by deal. Every deals_update that moves the stage is logged here automatically, so this is how you reconstruct how long a deal sat in each stage — the deal itself only carries its current one.

deal any
order.changedAt string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
departments_create write

Add a department, so people can be filed under it. `name` is required (up to 128 characters) and is UNIQUE across the org; `code` is optional (up to 64) and is ALSO unique — the short form an org already uses in its own spreadsheets (CEO, TECH, PROC). `manager` is an optional employee IRI from people_list. LIST FIRST AND EXPECT COLLISIONS: because both name and code are unique, re-posting a department that already exists FAILS rather than being idempotent, so an import that assumes create-per-row will stall the first time it meets a department the org already carries — typically one left over from a trial. Reconcile that row with departments_update instead of creating around it. THERE IS NO DELETE: the backend exposes no delete on a department, so a wrong name or code is corrected in place with departments_update and never removed. Requires ROLE_EMPLOYEES_MANAGER. Write.

name* string
code string | null
manager string | null
departments_list read

The org's departments, with the numeric id each one is referenced by. READ THIS BEFORE people_create or people_update: both accept a `department` IRI and there is no other way to discover a valid one. The collection is unpaginated and ordered by name, so a single call returns every department the org has. Filter by `name` (partial match) or `code` (exact). Rows carry id, name and code; `manager` is a relation and is not included in list rows — read it with people_list from the other side if you need it. Requires ROLE_EMPLOYEES_VIEWER. Read-only.

order.name string
id any
name string
code any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
departments_update write

Rename a department, give it a code, or set its manager. This is the tool that makes a department import possible rather than merely convenient: `name` and `code` are both unique, so a department the org already has — the single "HR" row a proof-of-concept tends to leave behind — cannot be created again, and the real list is reached by CORRECTING that row rather than colliding with it. Only the fields you send change, so passing `code` alone leaves the name intact. `id` is the numeric id from departments_list; `manager` is an employee IRI from people_list. THERE IS NO DELETE, which makes this the whole repair story: a department created with a typo is fixed here, and one that should not exist can only be renamed, not removed. Requires ROLE_EMPLOYEES_MANAGER. Write.

name string
code string | null
manager string | null
id* string | integer
holidayDaysLimits_create write

Give a person an allowance of one leave type, effective from a date. `seconds`, NOT days (#3763): an 8h day is 28800, so 21 days is 604800 and an overtime balance of 2h30 is 9000 — a figure that had nowhere to go while this was stored in whole days. `employee` and `holidayType` are IRIs (people_list and holidayTypes_list supply them); `variant` is the contract type the allowance belongs to (uop, b2b, uz, uod). To CORRECT an existing balance, add a row with a later dateFrom rather than editing the old one — the row in force is the latest one whose dateFrom has arrived, so history stays intact and a correction can be entered before it takes effect. (employee, holidayType, variant, dateFrom) is unique, so re-posting the same day replaces nothing and fails. Requires ROLE_HOLIDAYS_MANAGER. Write.

employee* string
seconds* integer
Leave allowance in SECONDS, not whole days (#3763).
holidayType string | null
variant* string
dateFrom string
notes string | null
holidayDaysLimits_get read

One entitlement row by id — the amount, the type, the contract variant and the date it takes effect. holidayDaysLimits_list finds the id. Amounts are in SECONDS (#3763). Read-only.

id* string | integer
holidayDaysLimits_list read

How much leave each person is ENTITLED to, per type — not how much they have taken, which is holidays_list. Filter by employee. A person can hold several rows for one type over time, because a balance gets topped up or corrected: the row IN FORCE is the one with the latest dateFrom that has already arrived, and rows dated ahead are deliberately ignored until then. Amounts are in SECONDS (#3763) — an 8h leave day is 28800. Requires ROLE_HOLIDAYS_MANAGER. Read-only.

employee any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
holidayDaysLimits_update write

Fix a row that was entered wrongly — a typo in the amount, the wrong variant. Amounts are in SECONDS (#3763). This is NOT how you record a balance CHANGING over time: for that, holidayDaysLimits_create a new row with a later dateFrom, which preserves what the previous balance was and when. Editing in place rewrites history and makes the old figure unrecoverable. holidayDaysLimits_list finds the id. Requires ROLE_HOLIDAYS_MANAGER. Write.

seconds integer
Leave allowance in SECONDS, not whole days (#3763).
holidayType string | null
variant string
dateFrom string
notes string | null
id* string | integer
holidayRequests_cancel read

Cancel a leave request — use it to clear a request that should never be acted on, such as a row left behind by a trial, a test, or someone who has left. TWO THINGS THAT SURPRISE PEOPLE. (1) IT DOES NOT DELETE THE ROW: the backend sets status to `canceled` rather than removing the row. BUT A CANCELLED REQUEST DISAPPEARS FROM holidayRequests_list — verified on production: afterwards neither the unfiltered list nor status=canceled returns it. So you cannot read back what you cancelled and there is no undo through the MCP; be sure of the id before calling. (2) IT IS NOT THE SAME AS REJECTING. Rejecting records a decision — it writes an approval-log entry naming you and MAILS THE EMPLOYEE that their leave was refused — whereas cancelling notifies only HR, and only when `notify-hr-managers-of-leave-activity` is on for the org. For a row that was never a genuine application, cancel is the honest and quieter one. ONLY WORKS ON A PENDING (`requested`) REQUEST when you are not its owner: an accepted request has already produced a Holiday that this does not remove, so cancelling one would leave a booked absence behind a request reading `canceled`. Requires ROLE_HOLIDAYS_MANAGER for someone else's request; the requester can always cancel their own. holidayRequests_list supplies the id. Write.

id* string | integer
holidayRequests_list read

Leave REQUESTS and where they stand — pending, approved, rejected. Distinct from holidays_list, which is booked leave: a request still awaiting a decision is not yet an absence, so plan against holidays_list and use this one to see what is waiting on someone. Supplies the holidayRequestId that holidays_approve and holidays_bulkApprove take. Read-only.

status any
employee any
type any
order.dateFrom string
order.dateTo string
order.type string
order.status string
project string
Filter holidays by project (accepts project id or IRI).
search string
Free-text search over the leave request's employee name, type and description.
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
holidays_active read

Who is off RIGHT NOW — every currently-running leave, org-wide, for everyone. This is the tool for 'who is out today', and the one to cross-check before treating resourcingBench_get's freePercent as availability, because the bench does not subtract leave. Unlike holidays_list it applies no project scoping and needs no permission beyond being signed in, so its answer covers the whole organisation. Returns each absence with its type and dates. Read-only.

employee any
type any
dateFrom.before string
dateFrom.strictly_before string
dateFrom.after string
dateFrom.strictly_after string
dateTo.before string
dateTo.strictly_before string
dateTo.after string
dateTo.strictly_after string
order.dateFrom string
order.dateTo string
order.type string
project string
Filter holidays by project (accepts project id or IRI).
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
holidays_create write

Record leave that a person is actually taking — the booked absence itself, not the entitlement (holidayDaysLimits_create) and not a pending application (holiday requests, which still need approving). What this writes is already-agreed time off, so it appears in holidays_list straight away and needs no approval step. `employee` is an IRI from people_list; `type` is a holidayTypes_list id. `dateFrom`/`dateTo` inclusive, and one call covers a whole range rather than a row per day. Two things bite: a type whose `descriptionRequired` is true (read holidayTypes_list first — `vacations` commonly is) REJECTS a create with no `description`; and `pick-up-day` is time already owed, so it does NOT consume the annual allowance the way `vacations` does — filing a day given back for a Saturday public holiday as `vacations` silently eats a day of someone's entitlement. Check holidays_list for the same person and dates before creating: this endpoint will happily record the same absence twice. EVERY CREATE MAILS THE EMPLOYEE, at their own company address, to say the absence was added — so loading a year of history someone already lived through arrives in their inbox row by row, and for staff who have not been invited yet it is the first they hear of Flowtly at all. Pass `notify: false` for a BACKFILL of absences that already happened; leave it alone when logging something new, because then the mail is the point. It suppresses the message only — the row, its `createdAt` and its payroll fact are written either way. Requires ROLE_HOLIDAYS_MANAGER. Write.

type* string
#4548. `Holiday` is writable directly (`holiday:create`), so it carries the same unbounded-free-text defect the request did. `max` tracks `length`.
employee* string
description string | null
#4548, and it is reachable two ways: written directly through `holiday:create`, and copied from the accepted request by `HolidayRequestDecider:47`. Both columns are VARCHAR(255), so bounding the request at 255 keeps that copy safe — this bounds the direct path.
dateFrom string
dateTo string
requestedSeconds integer | null
notify boolean
Should this absence tell the employee it was created? (#4705)
holidays_delete write

Remove a booked absence outright — the row is deleted, unlike holidayRequests_cancel which only flips a request's status. Use it to clear absences that should never have counted: demo or test rows left by a trial, or ones orphaned when their employee was deleted (people_delete detaches absences rather than removing them, so they survive with an empty employee name). THIS MOVES REAL NUMBERS: a booked absence is `payrollEligible` and consumes the person's entitlement, so deleting one changes their leave balance — the point when clearing test data, and a data-loss bug when the row was genuine. No undo, no notification. Read holidays_list first and be certain the row is not real history: a description in the org's own language, or dates matching an actual absence, usually means it is. Requires ROLE_HOLIDAYS_MANAGER. Write.

id* string | integer
holidays_get read

One leave record by id, with its type, dates and duration. Get the id from holidays_list or holidays_active. Read-only.

id* string | integer
holidays_list read

Booked leave over a period — the planning view, where holidays_active answers only about today. Filter by employee, by date range, or by project. WHAT YOU SEE DEPENDS ON YOUR PERMISSIONS, and a short list is not proof nobody is off: a holidays manager or accountancy viewer gets the organisation, while a project lead or viewer MUST pass a project filter (or ask about themselves) and is refused outright without one — that refusal is a permission boundary, not an empty calendar. Read-only.

employee any
type any
dateFrom.before string
dateFrom.strictly_before string
dateFrom.after string
dateFrom.strictly_after string
dateTo.before string
dateTo.strictly_before string
dateTo.after string
dateTo.strictly_after string
order.dateFrom string
order.dateTo string
order.type string
project string
Filter holidays by project (accepts project id or IRI).
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
holidayTypes_create write

Add a leave type the org does not yet offer — a sabbatical, unpaid childcare, a training day — so absences can be booked against it with holidays_create and an allowance granted with holidayDaysLimits_create. `name` (3–64 characters) is what people pick from when booking; `color` and `icon` are how it reads in the calendar; `reducesWorkingTime` false marks time off that does NOT lower the month's expected hours; and `descriptionRequired` true makes the type demand a reason, which holidays_create then enforces — see that tool for what it rejects. `status` defaults to `active`, so a type created without thinking about it is offered to everyone immediately. READ holidayTypes_list FIRST: types are org-wide, and THERE IS NO DELETE — a duplicate or a misspelt name can only be hidden again by setting status to inactive with holidayTypes_update, and it keeps every absence booked against it in the meantime. Requires ROLE_HOLIDAYS_MANAGER. Write.

status string
name* string
color string
THE API HAS TO ACCEPT ITS OWN OUTPUT (#4407).
descriptionRequired boolean
icon string | null
reducesWorkingTime boolean
Does an absence of this type reduce the month's expected working hours?
holidayTypes_list read

The leave types this org uses, with the id each one is referenced by. Read it before holidayDaysLimits_create/update, which need a holidayType IRI and will otherwise be guessed at. The one that is not a holiday in the ordinary sense is `pick-up-day` — time off owed for overtime already worked (Polish *odbior nadgodzin*), which is a GRANTED balance rather than an annual entitlement. Read-only.

order.name string
order.status string
order.descriptionRequired string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
holidayTypes_update write

Change a leave type, and above all TURN ONE BACK ON. `status` flips between `active` and `inactive`, and an inactive type is refused by holidays_create — so recording historic leave against a type the org has since retired starts here, and this is what unblocks a leave-history import rather than sending someone into the app UI. DEACTIVATING IS NOT DELETING, and there is no delete: absences already booked keep an inactive type and still read with it in holidays_list, so inactive means only 'not offered for new bookings'. THE TRAP THAT FOLLOWS FROM THAT: reactivate `vacations` to load last year's absences, forget to set it back to `inactive`, and you have not merely finished an import — you have changed what the organisation offers today, because every employee booking leave now sees that type on the list again. Set it back in the same session you imported. `descriptionRequired` also reaches into holidays_create, which refuses a booking with no description once it is on; turning it on leaves absences already recorded alone. `id` is the string id from holidayTypes_list (`vacations`, `not-paid`), and only the fields you send change. Requires ROLE_HOLIDAYS_MANAGER. Write.

status string
name string
color string
THE API HAS TO ACCEPT ITS OWN OUTPUT (#4407).
descriptionRequired boolean
icon string | null
reducesWorkingTime boolean
Does an absence of this type reduce the month's expected working hours?
id* string | integer
incomingInvoices_acceptAllSuggestions read

Accept every pending suggestion on an incoming invoice in one call — what a human does with the app's "accept all" button. The server applies, rebuilds, and applies again until nothing new appears: the transaction match does NOT exist until the supplier and amount are applied, so a single pass would leave the document unattached. Returns a report (what was applied, what was refused and why, and the transaction it ended up filed against). Pass dryRun to preview without writing. Never accepts supplier_create or a duplicate warning. Write.

dryRun boolean | null
Preview only: report what WOULD be applied, write nothing.
id* string | integer
incomingInvoices_applySuggestion read

Accept one of Flowtly's own suggestions on an incoming invoice — the same proposals a human sees in the app (supplier match, cost group, matching bank transaction, duplicate warning). Read them first with incomingInvoices_suggestions, then apply one by its id. Prefer this over guessing: Flowtly's matcher, not the agent, decides what is plausible. Write.

suggestionId string | null
action string | null
overrides array | null
id* string | integer
incomingInvoices_checkEInvoices read

Pull any new KSeF e-invoices into the org — what the app's "Sprawdź e-faktury" button does. Call this before concluding that a supplier's invoice is missing: without it you cannot tell "the supplier never sent it" from "our sync has not run yet". Returns once the fetch is queued; re-read incomingInvoices_list afterwards to see what arrived. Write.

No parameters.

incomingInvoices_create write

File an incoming (supplier) invoice or supporting document into accountancy — pass the bytes as base64 with a fileName and receivedAt. Flowtly OCRs it and suggests a supplier and a matching bank transaction. The file is fingerprinted as externalId 'upload_sha256:<sha256 of the bytes>': to avoid a duplicate, hash the bytes and check incomingInvoices_list for that externalId BEFORE uploading. Write.

fileBase64* string
The document's bytes, base64-encoded (a PDF, image, or XML invoice).
fileName* string
Filename for the upload, e.g. Invoice-2026-07-0007.pdf.
receivedAt* string
When the document reached the org (ISO-8601) — e.g. the date of the email that carried it.
mimeType string
MIME type of the bytes, e.g. application/pdf.
type string
invoice = the cost document itself. supporting_document = a payment receipt or annex filed alongside one.
contractor string | integer
Supplier this invoice is from — an id or IRI from suppliers_list. Optional: omit it and Flowtly's OCR suggests a supplier for a human to confirm.
transaction string | integer
Bank transaction this document settles — an id or IRI from transactions_list. Optional: omit it and Flowtly suggests a match.
currency string
Currency IRI, e.g. /currencies/1. Optional.
relatedMonth string
Accounting month this cost belongs to (ISO-8601 date). Optional.
dueDate string
Payment due date (ISO-8601). Optional.
note string
Free-text note. Optional.
incomingInvoices_get read

Get one incoming (supplier) invoice or supporting document by id, with its OCR'd fields and current match state.

id* string | integer
incomingInvoices_list read

List incoming (supplier) invoices and supporting documents — the accountancy inbox. An incoming invoice IS a document attached to a bank transaction, so exists.transaction=false is how you find documents that are not yet matched to a payment. Filter also by status, relatedMonth, counterparty, project, tags, or hasDetectedProblems. Each document is fingerprinted as externalId 'upload_sha256:<sha256 of the bytes>' — hash a file and look for that externalId here BEFORE incomingInvoices_create, or you will file a duplicate.

exists.transaction boolean
hasDetectedProblems boolean
properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
status string
Filter transaction attachment by status based on custom logic
relatedMonth string
relatedMonth.before string
relatedMonth.strictly_before string
relatedMonth.after string
relatedMonth.strictly_after string
receivedAt.before string
receivedAt.strictly_before string
receivedAt.after string
receivedAt.strictly_after string
tags string
order.receivedAt string
order.name string
order.amount string
name string
invoiceNumber string
counterparty any
project string
budget string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
incomingInvoices_matchCandidates read

List the bank transactions that could be the payment for this incoming invoice, ranked by the backend's own matcher. Reach for it when a document has no transaction attached and you need to choose one; prefer these candidates over guessing from amounts yourself.

exists.transaction boolean
hasDetectedProblems boolean
properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
status string
Filter transaction attachment by status based on custom logic
relatedMonth string
relatedMonth.before string
relatedMonth.strictly_before string
relatedMonth.after string
relatedMonth.strictly_after string
receivedAt.before string
receivedAt.strictly_before string
receivedAt.after string
receivedAt.strictly_after string
tags string
order.receivedAt string
order.name string
order.amount string
name string
invoiceNumber string
counterparty any
project string
budget string
id* string | integer
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
incomingInvoices_suggestions read

Read Flowtly's own proposals for an incoming invoice — supplier match, cost group, matching bank transaction, duplicate warning. These are exactly the proposals a human sees in the app. Read them first, then apply one by id with incomingInvoices_applySuggestion, or take them all with acceptAllSuggestions. Pass refresh to recompute rather than serve the cached set.

exists.transaction boolean
hasDetectedProblems boolean
properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
status string
Filter transaction attachment by status based on custom logic
relatedMonth string
relatedMonth.before string
relatedMonth.strictly_before string
relatedMonth.after string
relatedMonth.strictly_after string
receivedAt.before string
receivedAt.strictly_before string
receivedAt.after string
receivedAt.strictly_after string
tags string
order.receivedAt string
order.name string
order.amount string
name string
invoiceNumber string
counterparty any
project string
budget string
refresh boolean
Force a rebuild instead of returning the cached suggestions. Suggestions are cached; this is the only way to regenerate ones that are stale or wrong. The transaction-match rebuild runs synchronously; the OCR/LLM rebuild is dispatched async.
id* string | integer
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
incomingInvoices_suggestionsDebug read

Explain WHY an incoming invoice's suggestions came out as they did — the matcher's scoring, for diagnosing a missing or wrong suggestion. Diagnostic only; use incomingInvoices_suggestions for normal work.

id* string | integer
initialBudgetItems_list read

List initial budget line items — the planned amounts, by tag, that contractComparison comes back against. Needs ROLE_BUDGETS_VIEWER. Read-only.

initialBudget any
tagDefinition any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
initialBudgets_contractComparison read

PLANNED versus CONTRACTED, per tag — the initial budget's planned amounts against the sum of contract values actually signed for that project. This is the question 'have we committed more than we budgeted, and where', and it reads directly off the contracts already in the org, so importing contracts makes it answerable without any further work. Amounts are grosze; a mixed-currency project produces a notice rather than a silently wrong total. Needs ROLE_BUDGETS_VIEWER. Read-only.

id* string | integer
initialBudgets_get read

Get one initial budget by id, with its items. Needs ROLE_BUDGETS_VIEWER. Read-only.

id* string | integer
initialBudgets_list read

List initial budgets — the ORIGINAL plan for a project or investment, as opposed to the live budget it is measured against. Needs ROLE_BUDGETS_VIEWER. Read-only.

project any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
invoiceTransactions_create write

Record a payment against an outgoing (sales) invoice. `invoice` is an invoice IRI from invoices_list; `date` is when the payment is treated as made. `transaction` is optional — omit it to record settlement with no bank line, which is what you want for historical invoices whose bank statement was never imported. `amount` is optional and defaults to the invoice's outstanding amount. Recording a payment is what stops an issued, past-due invoice being treated as unpaid, so it is also what stops payment reminders being queued for it. Nothing prevents recording two payments against one invoice, so read invoices_get first if you are unsure whether one is already settled. Write.

invoice* string
transaction string | null
amount number
MINOR units (grosze/cents) — 2 606,74 is 260674, NOT 2606.74. This differs from every neighbouring money field on this API: Invoice.amount, Invoice.amountNet and Transaction.amount are all MAJOR units, so a caller copying a transaction amount straight into this field stores 100x too little and the invoice renders as 1% paid.
date* string
invoiceTransactions_delete write

Delete a payment record from an invoice by id — read the ids from invoices_get's invoiceTransactions. This removes the RECORD THAT AN INVOICE WAS PAID, not a bank transaction: use it when an invoice carries a payment that should never have existed, the usual case being the same payment booked twice — once by hand and once by the statement import that later matched it. Check invoices_get first and delete the record whose `transaction` is the wrong one (keep the one pointing at the real imported bank line); deleting the last remaining payment makes the invoice unpaid again, which re-arms payment reminders for it. Requires ROLE_INVOICES_MANAGER. Irreversible, high-impact. Write.

id* string | integer
invoiceTransactions_update write

Update an existing invoice-payment record by id (from invoices_get's invoiceTransactions, or by paging invoiceTransactions). The most common use: point a payment recorded with no bank line at a transaction you just imported via transactions_importStatement, by setting `transaction` to a transaction IRI/id from transactions_list. THE FOOTGUN: this is a PATCH, but the backend still requires `invoice` and `date` on every call — it does NOT merge in the existing values for you. Read the record first (or already have it from the create call) and resend its `invoice` and `date` unchanged alongside whatever you actually mean to change, or the update is rejected. `transaction` accepts null to unlink a payment from a bank line. `amount` is optional. Write.

invoice string
transaction string | null
amount number
MINOR units (grosze/cents) — 2 606,74 is 260674, NOT 2606.74. This differs from every neighbouring money field on this API: Invoice.amount, Invoice.amountNet and Transaction.amount are all MAJOR units, so a caller copying a transaction amount straight into this field stores 100x too little and the invoice renders as 1% paid.
date string
id* string | integer
leadActivities_bulkImport read

Record a whole outbound wave — every message you actually sent — in ONE call, instead of one leadActivities_create per message. Pass an array; each row names its lead (leadCompanyName, matched against an EXISTING lead, or a lead IRI) plus type and occurredAt. Give every row an externalId — the stable per-message id, e.g. the Gmail message id — and the import is idempotent: re-running it, or re-running a wave that was only partly imported, reports duplicates instead of creating them. Rows without an externalId dedupe on (lead, type, occurredAt, contact), the same natural key leads_bulkImport uses, so a wave that first landed through that tool is not duplicated here. Every row gets its own outcome (created | duplicate | error), so one malformed row does not discard the rest of the batch. Does NOT create leads — use leads_bulkImport for that. ≤ 1000 rows/call. Write.

activities* array
leadActivities_byList read

Every lead activity on a CAMPAIGN (a lead list), in one call — pass the list's id, IRI, or exact name. leadActivities_list filters by a single lead, so campaign-level reporting otherwise costs one call per member (302 for a list like PZFD); this resolves the list's members and reads their activities in bounded batches instead. Combine with type and occurredAt.after/.before to get the counts people actually ask for: reply rate (type=reply_received), bounce rate (type=bounced), send coverage (type=message_sent). Returns listId, listName, leadCount, and the merged activities sorted by occurredAt. An unknown list is an ERROR, not an empty result — so a mistyped name cannot read as "this campaign had no activity". Ids come from leadLists_list. Read-only.

list* string
Lead list id, IRI, or exact name. An unknown list is an error, not an empty result.
membershipStatus any
Restrict to members with this outreach status (pending, contacted, replied, bounced, do_not_contact).
type any
Activity type, e.g. message_sent, reply_received, bounced, follow_up.
channel any
contact string
threadId string
externalId string
occurredAt.after string
occurredAt.before string
occurredAt.strictly_after string
occurredAt.strictly_before string
order.occurredAt string
leadActivities_create write

Log ONE outreach touch on a lead — an invite sent, an invite accepted, a message, a reply, a call, a follow-up (lead + type + occurredAt required; channel, contact, body optional). THIS is where a prospect's outreach history belongs: a crmNote is freeform commentary, an activity is the structured, filterable touch-log the prospecting queue timeline renders. Do NOT narrate touches into a note. type: invite_sent | invite_accepted | message_sent | reply_received | call | meeting | follow_up | …; channel: linkedin | email | phone | …. Write.

lead* string
contact string | null
wave string | null
type* string
channel string | null
occurredAt* string
body string | null
externalId string | null
threadId string | null
fromMailbox string | null
leadActivities_delete write

Delete a logged outreach activity by id. Write.

id* string | integer
leadActivities_get read

Get one lead activity (outreach touch) by id.

id* string | integer
leadActivities_list read

List a lead's outreach touches — its activity timeline (invite sent, replies, calls, follow-ups). Filter by lead to read one prospect's history. This is the structured counterpart to crmNotes_list: activities are the typed, dated touch-log; notes are freeform commentary.

lead any
contact any
type any
channel any
externalId any
threadId any
wave any
lead.memberships.list any
occurredAt.before string
occurredAt.strictly_before string
occurredAt.after string
occurredAt.strictly_after string
order.occurredAt string
order.type string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
leadActivities_update write

Update a logged outreach activity by id (type, channel, occurredAt, body). Write.

contact string | null
type string
channel string | null
occurredAt string
body string | null
externalId string | null
threadId string | null
fromMailbox string | null
id* string | integer
leadContacts_create write

Add a contact person to a lead (lead + name required; email, phone, role, linkedinUrl, isPrimary optional). A contact's LinkedIn URL belongs in linkedinUrl, NOT in a crmNote. Write.

lead string
name* string
email string | null
phone string | null
linkedinUrl string | null
role string | null
isPrimary boolean
leadContacts_delete write

Delete a lead contact by id. Write.

id* string | integer
leadContacts_get read

Get one lead contact by id.

id* string | integer
leadContacts_list read

List the contact people attached to leads. Filter by lead to read one prospect's contacts, or by email to find which lead a message came from.

lead any
email any
linkedinUrl any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
leadContacts_update write

Update a lead contact by id — e.g. set linkedinUrl / email / phone once you find them. Write.

lead string
name string
email string | null
phone string | null
linkedinUrl string | null
role string | null
isPrimary boolean
id* string | integer
leadListMemberships_create write

Add a lead to an outbound list (list + lead required; status optional). Any lastContactedAt you pass is a snapshot that nothing will advance afterwards — log the touch as a lead activity as well, or it stays unqueryable. Write.

list* string
lead* string
status string
lastContactedAt string | null
leadListMemberships_delete write

Remove a lead from an outbound list. Write.

id* string | integer
leadListMemberships_get read

Get one lead-to-list membership by id. Its status and lastContactedAt are a caller-written snapshot, not live state — see leadListMemberships_list.

id* string | integer
leadListMemberships_list read

List which leads sit on which outbound prospecting lists. Filter by list, lead or status. CAUTION: status and lastContactedAt are a SNAPSHOT written by whoever last imported or updated the membership. They are not derived, and nothing advances them when an activity is recorded — logging a wave of 529 follow-ups moves neither field — so they can be arbitrarily far behind. To answer “when did we last touch this prospect”, read the activity log instead: leadActivities_list for one lead, leadActivities_byList for a whole campaign. leadListMemberships_syncFromActivities reports the gap and can close it.

list any
lead any
status any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
leadListMemberships_update write

Update a lead’s membership in a list — e.g. set outreach status (contacted/replied/bounced). status and lastContactedAt are caller-maintained: what you write stands until someone writes again, and recording lead activities does NOT update them. Write.

list string
lead string
status string
lastContactedAt string | null
id* string | integer
leadLists_create write

Create an outbound prospecting list (name required). Write.

name* string
description string | null
owner string | null
channel string | null
status string
segment string | null
leadLists_delete write

Delete an outbound list by id. Write.

id* string | integer
leadLists_get read

Get one outbound prospecting list by id.

id* string | integer
leadLists_list read

List outbound prospecting lists. Use it to resolve the list id that leadListMemberships_create takes.

status any
channel any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
leadLists_update write

Update an outbound list by id. Write.

name string
description string | null
owner string | null
channel string | null
status string
segment string | null
id* string | integer
leadLostReasons_get read

Get one lead lost-reason by id.

id* string | integer
leadLostReasons_list read

List the reasons a lead can be marked lost, in order.

order.position string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
leads_bulkImport read

Import many leads in ONE call, each with its contacts, list membership and outreach activities nested — the server creates the lead then threads its id into the children, so you never juggle intermediate IRIs. Idempotent by natural keys (companyName / email / (list,lead) / (type,occurredAt,contact)): safe to re-run and to chunk (≤100 leads/call). This is the bulk path a campaign import should use instead of N leads_create calls. Write.

list object
leads* array
leads_convert read

Convert a qualified lead into a Client + one contact per lead-contact + an open Deal. Requires an existing client (the lead’s client or a clientId in the body). Write.

companyName string | null
website string | null
source string | null
owner string | null
client string | null
industry string | null
linkedinUrl string | null
doNotContact boolean
getDoNotContact(), NOT isDoNotContact() — see the property's comment. An is*() accessor is invisible to API Platform's readability check and the flag would be silently dropped from every payload.
companySize string | null
stage string | null
id* string | integer
clientId string
Optional id of an EXISTING client to link the lead to. Omit it and the lead's own client is used, or a new Client is created from the lead.
title string
Optional title for the Deal this creates. Defaults to the lead's companyName.
leads_create write

Create a lead (outbound/inbound prospect target; companyName, source, owner, linked client optional). A new lead is always status=open — status is not settable here, and moves only through leads_convert, leads_lose and leads_reopen. Write.

companyName string | null
website string | null
source string | null
owner string | null
client string | null
industry string | null
linkedinUrl string | null
doNotContact boolean
getDoNotContact(), NOT isDoNotContact() — see the property's comment. An is*() accessor is invisible to API Platform's readability check and the flag would be silently dropped from every payload.
companySize string | null
stage string | null
leads_dedupeCheck read

Check whether a prospect is already in the CRM, using the same filters as leads_list (companyName, source, owner, …). Call this BEFORE leads_create: a duplicate lead splits the outreach history across two records, and nothing downstream will merge them for you.

status any
source any
owner any
client any
companyName string
linkedinUrl any
doNotContact boolean
createdAt.before string
createdAt.strictly_before string
createdAt.after string
createdAt.strictly_after string
closedAt.before string
closedAt.strictly_before string
closedAt.after string
closedAt.strictly_after string
order.createdAt string
order.companyName string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
leads_delete write

Delete a lead by id (soft delete). Write.

id* string | integer
leads_get read

Get one lead by id — company, website, source, status, owner and the client it converted to, if any.

id* string | integer
leads_list read

List leads — prospect targets, before qualification. Filter by status, source, owner, client, companyName, or createdAt/closedAt ranges. A qualified lead becomes a Client plus an open Deal via leads_convert; until then it lives only here, not in clients_list.

status any
source any
owner any
client any
companyName string
linkedinUrl any
doNotContact boolean
createdAt.before string
createdAt.strictly_before string
createdAt.after string
createdAt.strictly_after string
closedAt.before string
closedAt.strictly_before string
closedAt.after string
closedAt.strictly_after string
order.createdAt string
order.companyName string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
leads_lose read

Close a lead as LOST — sets status=lost and stamps closedAt. REQUIRES lostReasonId, the `id` of a leadLostReasons entry (run leadLostReasons_list first; it is a picklist, so free text is refused with 422). This is the ONLY way to record a lead as lost: leads_update ignores status, and doNotContact means "never contact again", which is a different and much stronger statement than "we did not win this one". It does NOT move the lead's stage — LeadStage has no terminal flag, so the lead keeps its funnel position and leads_reopen can restore it exactly. Write.

companyName string | null
website string | null
source string | null
owner string | null
client string | null
industry string | null
linkedinUrl string | null
doNotContact boolean
getDoNotContact(), NOT isDoNotContact() — see the property's comment. An is*() accessor is invisible to API Platform's readability check and the flag would be silently dropped from every payload.
companySize string | null
stage string | null
id* string | integer
lostReasonId* string
REQUIRED. The id of a leadLostReasons entry — run leadLostReasons_list and pass its `id`, e.g. the entry labelled "Chose a competitor". Without it the call is refused with 422 "A lost reason is required."; the reason is a picklist entry, not free text.
leads_reopen read

Undo leads_lose — sets status back to open and clears closedAt and the lost reason. The stage is untouched, so the lead resumes exactly where it was. Reach for this when a lead was closed against the wrong record or the prospect came back. Write.

companyName string | null
website string | null
source string | null
owner string | null
client string | null
industry string | null
linkedinUrl string | null
doNotContact boolean
getDoNotContact(), NOT isDoNotContact() — see the property's comment. An is*() accessor is invisible to API Platform's readability check and the flag would be silently dropped from every payload.
companySize string | null
stage string | null
id* string | integer
leads_update write

Update a lead by id (company, website, source, owner, linked client, stage, doNotContact). NOT status or lostReason: those are refused by the entity and silently ignored by this endpoint, so closing a lead needs leads_lose (with a lostReasonId) and undoing that needs leads_reopen. Moving `stage` walks the funnel; it does not close the lead. Write.

companyName string | null
website string | null
source string | null
owner string | null
client string | null
industry string | null
linkedinUrl string | null
doNotContact boolean
getDoNotContact(), NOT isDoNotContact() — see the property's comment. An is*() accessor is invisible to API Platform's readability check and the flag would be silently dropped from every payload.
companySize string | null
stage string | null
id* string | integer
leadStages_get read

Get one lead stage by id.

id* string | integer
leadStages_list read

List the stages a lead moves through, in order. Leads have their own stage set — deals use stages_list, which is a different thing.

order.position string
order.label string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
locations_create write

Create a location (name required; optional officeOpenHour/officeCloseHour as seconds past midnight). Use the real address rather than a project or investment name — this is what someone standing in front of the asset needs, and the project name is already carried elsewhere. Requires ROLE_LOCATIONS_MANAGER. Write.

name* string
officeOpenHour integer | null
officeCloseHour integer | null
locations_get read

Get one location by id — its name and office hours. Read-only.

id* string | integer
locations_list read

List the org's locations — the physical places assets sit, shown in the UI as Lokalizacja. Needs ROLE_LOCATIONS_MANAGER, which unusually gates the READ as well as the write. Read-only.

order.name string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
locations_update write

Rename a location or change its office hours. Requires ROLE_LOCATIONS_MANAGER. Write.

name string
officeOpenHour integer | null
officeCloseHour integer | null
id* string | integer
organization_whoami read

Return the organization this MCP connection is bound to — { orgId, name, slug, userId }. Call it to confirm WHICH tenant you are about to write into before any create/update: the connection is pinned to exactly one org by the token, and writing prospects/records into the wrong org is a real incident. Read-only.

No parameters.

organizationAddresses_get read

Get one subscription address record by id — name, street, city, postCode, country, and the tax fields. `street` carries the building number when it was entered by hand, and does not when it came from the NIP/GUS lookup. Read-only.

id* string | integer
organizationAddresses_list read

List the organization's subscription address records — the address attached to the Flowtly subscription, and the source the mail-footer {{organizationAddress}} renders from. Normally exactly one row. This is NOT the invoice seller address, which lives in the organization-billing-* config keys (configs_get) and is what invoices and KSeF read; the two are maintained separately and routinely disagree. Read both before concluding which one a customer actually edited. Read-only.

organization any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
organizationAddresses_update write

Update the organization's SUBSCRIPTION address record (id required; send only the fields you are changing). THIS IS THE RECORD THE MAIL FOOTER RENDERS FROM: the footer's {{organizationAddress}} is composed as "street, postCode city" from here, NOT from the organization-billing-* config keys that invoices and KSeF use as the seller address. The two stores drift, and the footer reading this one is a known defect — so when a signature shows an address the customer swears they corrected, they corrected the billing keys and this is the record still holding the old value. `street` is a single free-text column that must carry the building number too: the NIP/GUS lookup fills only the street name and silently discards the building and flat number, which is why addresses here read "ul. Example" with no number. Write the full "ul. Example 8/12" to repair it. READ FIRST with organizationAddresses_list and compare against configs_get on organization-billing-street before writing, so you copy the customer's own maintained value rather than inventing one. Requires ROLE_BILLINGS_MANAGER. Write.

type string
name string
street string
city string
postCode string
country string
email string | null
tin string | null
tinCountry string | null
id* string | integer
organizationIcon_upload read

Upload/replace the organization's icon/favicon (base64 image + contentType + filename). Read the current one via configs_get organization-icon-url. Write.

bytesBase64* string
The image file bytes, base64-encoded.
contentType* string
The image MIME type, e.g. image/png, image/jpeg, image/svg+xml.
filename* string
A filename for the upload, e.g. logo.png.
organizationLogo_upload read

Upload/replace the organization's logo (base64 image + contentType + filename). Read the current one via configs_get organization-logo-url. Write.

bytesBase64* string
The image file bytes, base64-encoded.
contentType* string
The image MIME type, e.g. image/png, image/jpeg, image/svg+xml.
filename* string
A filename for the upload, e.g. logo.png.
organizationMailFooter_get read

Read the organization's outgoing-mail footer text (the block appended to mail Flowtly sends on the org's behalf).

No parameters.

organizationMailFooter_update write

Update the organization's outgoing-mail footer text. Write.

mailFooter string
organizations_get read

Get an organization by id. WARNING — this does NOT tell you which organization you are connected to. An OAuth connection is pinned to exactly one org (token-bound), but this endpoint returns any org the connected USER is a member of, so a successful read here reads like confirmation you are working in that org when you may not be. To verify the tenant you are actually operating on, read tenant-scoped data instead — people_list or clients_list — and never start a bulk write on the strength of this call alone.

id* string | integer
paymentScheduleLines_create write

Add one instalment to a contract's payment schedule — the plan of what is expected to be invoiced or paid, and when. Pass the contract IRI, a date and an amount. This is what clears the missing-payment-schedule problem contracts_get reports on a non-cyclic contract: a one-shot fee still has a schedule, it is simply a single line for the whole amount on the day it falls due. Cyclic contracts are not checked for one, because the system does not auto-generate lines from a cadence. AMOUNT IS IN MINOR UNITS — grosze, not złote: 5 300,00 is "530000", and "5300" silently books a 53,00 line. The API returns them the same way, so read one back with contracts_paymentScheduleLines if you are unsure of the scale. Read the result back with contracts_paymentScheduleLines. Write.

contract string
IRI of the contract this instalment belongs to, e.g. `/contracts/0f7d9c1e-...`. A bare id is NOT promoted on a request body — send the full path. The backend requires it (the column is not nullable) even though the spec declares no required list.
date string
When the instalment falls due. Date-time; the date part is what matters.
amount* string
MINOR UNITS as a decimal string — grosze, not złote. 5 300,00 is "530000"; writing "5300" creates a 53,00 line. Carries no currency of its own — the contract's currency applies.
note string | null
Free-text label for the instalment, e.g. the milestone it hangs off. Optional.
paymentScheduleLines_delete write

Remove one payment schedule line by id. Deletes the PLAN, not the money: an invoice or transaction already matched to the line is not affected, but it stops being reconciled against anything. Prefer paymentScheduleLines_update for an instalment that moved. Write.

id* string | integer
paymentScheduleLines_import read

Load a contract's whole instalment plan in one call, instead of one round trip per line. Built for developer contracts, which are paid in construction tranches — a single sale is six to twelve instalments, and a register of them is hundreds. Each row names its contract BY NAME (for an imported developer contract, its agreement number), a due date, and an amount in MINOR UNITS — grosze, so 5 300,00 is "530000" and "5300" silently books 53,00. Rows reconcile against the lines already there on contract+date+amount+note, so an unknown line is created, an identical one is skipped, and re-running the same batch changes nothing; PaymentScheduleLine has no external-reference column, so that natural key is the reconcile key. A row whose contract name matches nothing, or matches MORE than one contract, is reported failed rather than attached to a guess — putting an instalment on the wrong contract misstates two cashflows at once. Pass dryRun:true first on a real load. Max 1000 rows. Write.

lines* array
The instalments, in any order. Each is matched to its contract BY NAME and reconciled against the lines already there, so an unknown line is created, an identical one is skipped, and re-running the same batch changes nothing.
dryRun boolean
Preview without writing. Reports would-create / skipped / failed per row and creates nothing.
paymentScheduleLines_update write

Change one payment schedule line by id — its date, amount or note. Use it when an instalment slips or is renegotiated, rather than deleting and recreating, so the line keeps any invoice already matched to it. AMOUNT IS IN MINOR UNITS — grosze, not złote: 5 300,00 is "530000", and "5300" silently books a 53,00 line. The API returns them the same way, so read one back with contracts_paymentScheduleLines if you are unsure of the scale. Write.

date string
When the instalment falls due.
amount string
MINOR UNITS as a decimal string — grosze, not złote. 5 300,00 is "530000".
note string | null
Free-text label for the instalment. Optional.
id* string | integer
people_create write

Create a person/employee record (firstname + lastname required; optional companyEmail, contactEmail, contactPhone). Write.

companyEmail string | null
contactEmail string | null
contactPhone string | null
birthday string | null
firstname* string | null
lastname* string | null
phoneTextAllowed boolean
tracksTime boolean
Whether this employee is expected to log working time. When false, the time-tracking dashboard widgets (project timer + monthly working-hours) are hidden for them. Defaults to true so existing employees keep their current behaviour.
excludedFromProjects boolean
Whether this employee is excluded from project assignment. When true they are not assignable to projects: the Resourcing bench stops offering them and the time-logging assignee picker can filter them out. Defaults to false — included — so every existing employee keeps their current behaviour. This narrows FUTURE assignability only; it never touches an excluded person's existing allocations, which stay real facts.
department string | null
reportsTo any
defaultProject string | null
Project this employee's working time is assigned to by default when not logged against a specific project. Stored only for now — no automatic assignment is wired up yet.
people_delete write

Delete an employee/person record by id (e.g. to remove a placeholder/dummy employee). Requires ROLE_EMPLOYEES_MANAGER; the backend runs a delete processor that also detaches related records. High-impact, irreversible. Write.

id* string | integer
people_get read

Get one person/employee record by id — names, emails, phone, manager, and whether they are active.

id* string | integer
people_getPermissions read

What a person can actually do, resolved: their permission groups (each with the roles it grants), their per-person overrides, and the effectiveRoles the two combine into. THE way to check whether an access change landed — people_list shows a roles field, but this is the one that explains WHY it holds those roles and which lever to pull to change it. Reach for it before every people_setRoleOverrides call, because that tool replaces the override lists wholesale and this is where you read the current ones. staleOverrides are removed-overrides that no longer match any group-granted role, so they currently do nothing. people_list supplies the id. Requires ROLE_ROLES_MANAGER to view anyone but yourself. Read-only.

id* string | integer
Person/employee id (or IRI). people and employees share the same id.
people_invite read

Give an existing person a LOGIN: creates a pending organization invitation and emails it to them, in the org's configured UI language. This is the step people_create and people_setPermissionGroups do NOT do — a person with permission groups still cannot sign in until they are invited and accept. Requires the person's email; fails if they already have a login. Onboarding order: people_create (record) -> people_invite (login) -> people_setPermissionGroups (rights). Write.

id* string | integer
Person/employee id (or IRI) to give a login to. people and employees share the same id.
email* string
Address the invitation is sent to, and the address the person will sign in with. Usually the person's companyEmail — read it off people_get rather than guessing.
firstname string
Optional; shown in the invitation mail. Defaults to the person's own firstname when omitted.
lastname string
Optional; shown in the invitation mail. Defaults to the person's own lastname when omitted.
phone string
Optional phone number recorded on the invited user.
people_list read

List people/employees. Filter by isActive, reportsTo (a manager's id), projectMembers.project, or search; page with cursor. People and employees share the same id, so this is how you resolve the employee id that work time, responsibilities, project membership and permission tools all expect.

isActive boolean
id any
firstname string
lastname string
reportsTo any
projectMembers.project any
exists.userId boolean
exists.reportsTo boolean
exists.companyEmail boolean
excludedFromProjects boolean
search string
Free-text search over employee name and company email.
properties array
Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: properties[]={propertyName}&properties[]={anotherPropertyName}&properties[{nestedPropertyParent}][]={nestedProperty}
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
people_setPermissionGroups read

Set (replace) a person's WHOLE permission-group set by numeric group ids (see permissionGroups_list — e.g. the "Business Owner" group grants ROLE_ADMIN): pass every group they should end up with, and [] removes all of them. Grants access; does NOT create a login or email the person — that is people_invite. THE TRAP: giving someone their FIRST group moves them onto the computed model, where roles come from groups and per-person overrides, and a role granted to them by hand outside that model disappears in the very same call — a ROLE_ADMIN handed to one person is exactly the kind this removes. It cuts the other way too: clearing their last group moves them back off it and makes those older roles reappear. The overridesAdded/overridesRemoved lists say nothing about any of this; they describe overrides and stay empty while effective access changes. So the response reports the difference between the roles the person held before this call and after it, as rolesLost and rolesGained — that is the pair to read once the call returns. rolesLost null (not []) means the snapshot taken before the write could not be read and the delta is UNKNOWN, with the reason in roleDeltaUnavailable: the group change still happened, so a null is not a clean bill of health — re-check with people_getPermissions. To give back a role that should have survived, grant it with people_setRoleOverrides. Requires ROLE_ROLES_MANAGER. Write.

id* string | integer
Person/employee id (or IRI). people and employees share the same id.
groupIds* array
Numeric permission-group ids the person should have (see permissionGroups_list). REPLACES their current set — pass every group they should keep. Empty array removes all groups.
people_setRoleOverrides read

Set (replace) the roles ONE person gets on top of — or has taken away from — their permission groups. Reach for a group first (people_setPermissionGroups): groups are the intended abstraction and scale to more than one person, so use an override only where a single individual genuinely differs from every group. REPLACES both lists wholesale, so read people_getPermissions first and pass back every override they should keep; omitting a list clears it. Roles are ROLE_ constants — permissionGroups_list shows the ones this org already uses. A role in both added and removed is refused rather than guessed at. Returns the same resolved snapshot as people_getPermissions, so you can confirm the result without a second call. Does NOT create a login — see people_invite. Requires ROLE_ROLES_MANAGER. Write.

id* string | integer
Person/employee id (or IRI). people and employees share the same id.
added array
Roles this person gets ON TOP OF their groups. REPLACES the whole added-set — read people_getPermissions first and pass back every override they should keep. Omit or pass [] to clear.
removed array
Roles to take away from this person even though a group grants them. REPLACES the whole removed-set. Omit or pass [] to clear.
people_update write

Update a person/employee record by id (name, companyEmail, contactEmail, contactPhone, etc.). Write.

companyEmail string | null
contactEmail string | null
contactPhone string | null
birthday string | null
firstname string | null
lastname string | null
phoneTextAllowed boolean
tracksTime boolean
Whether this employee is expected to log working time. When false, the time-tracking dashboard widgets (project timer + monthly working-hours) are hidden for them. Defaults to true so existing employees keep their current behaviour.
excludedFromProjects boolean
Whether this employee is excluded from project assignment. When true they are not assignable to projects: the Resourcing bench stops offering them and the time-logging assignee picker can filter them out. Defaults to false — included — so every existing employee keeps their current behaviour. This narrows FUTURE assignability only; it never touches an excluded person's existing allocations, which stay real facts.
department string | null
reportsTo any
defaultProject string | null
Project this employee's working time is assigned to by default when not logged against a specific project. Stored only for now — no automatic assignment is wired up yet.
id* string | integer
permissionGroups_create write

Create a permission group (name required; roles = list of ROLE_* strings it grants). Write.

code string | null
Stable code for default seed groups; null for org-created custom groups.
name* string
description string | null
roles array
ROLE_* string values
permissionGroups_get read

Get one permission group by id, including the ROLE_* strings it grants.

id* string | integer
permissionGroups_list read

List the org's permission groups and the roles each one grants — e.g. the "Business Owner" group grants ROLE_ADMIN. Read this before people_setPermissionGroups: the roles in the response are the authority on what a group actually permits, so you never have to guess from its name.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
permissionGroups_update write

Update a permission group's name, description, or granted roles by id. Write.

name string
description string | null
roles array
ROLE_* string values
id* string | integer
pipelines_get read

Get one sales pipeline by id.

id* string | integer
pipelines_list read

List sales pipelines. A pipeline owns an ordered set of stages — read them with stages_list filtered by pipeline.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
positions_list read

List positions — the named roles (e.g. "Backend Engineer") that a project allocation fills. No filters; Position has pagination disabled, so this always returns the org's full role catalog in one call. Each item is {id, name, roles}. Use it to resolve the position name behind an allocations_list row's positionId, and to find the position id a resourcing import must match against.

order.name string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
projectMembers_create write

Put a person ON a project (employee + project IRIs required, e.g. "/people/204" and "/projects/243"; optional position = employee|tech-lead|account-manager|viewer, default employee). THIS IS THE ACCESS CONTROL, not a label: a person who is not a member does not see the project at all — it is missing from their Projects list and they cannot log time against it — so this is the tool that restores someone locked out of a project. POSITION IS NOT COSMETIC: a user holding a project-scoped role sees only the projects where their membership position matches it — ROLE_PROJECTS_LEAD matches tech-lead, ROLE_PROJECTS_VIEWER matches viewer — so giving a project lead an `employee` row leaves them just as blind as no row at all. Membership does NOT cascade: putting someone on a parent folder gives them nothing on the projects underneath it, so a folder tree needs one call per project. The unique key is (employee, project, position), which means positions stack rather than replace — a person can hold employee AND tech-lead on the same project as two separate rows, and adding tech-lead to someone who is already an employee there does not remove or upgrade the employee row (use projectMembers_update to change a position in place). Read the current rows with projectMembers_list?project=/projects/{id} first, or projects_get, whose projectMembers array carries each row's id. NOT SILENT: adding a person who is not yet on the project dispatches a project-assigned notification to them, so a 17-project backfill sends 17 notifications. Requires ROLE_PROJECTS_MANAGER. Write.

employee* string
project* string
position string
positionName string
notify boolean
Whether to send this person the "project assigned" notification (#3955).
projectMembers_delete write

Take a person OFF a project by membership id — find it with projectMembers_list or in projects_get's projectMembers array. This REVOKES ACCESS: once the last membership row for that person on that project is gone, the project disappears from their view and they can no longer log time against it, which is exactly how a project silently vanishes for someone. Hours already logged are NOT deleted and stay on the project; the person simply can no longer see or add to them. Deleting one position leaves any other position the same person holds on the same project intact. Requires ROLE_PROJECTS_MANAGER. Irreversible (recreating makes a new row and re-notifies), high-impact. Write.

id* string | integer
projectMembers_get read

Get one project membership by id — its employee, project and position. Ids come from projectMembers_list or the projectMembers array on projects_get.

id* string | integer
projectMembers_list read

List project memberships — WHO CAN SEE WHICH PROJECT. Filter by project (`/projects/{id}`) to read one project's roster, or by employee to read every project one person can reach; each row carries its own id, the employee, the project and the position (employee|tech-lead|account-manager|viewer). Reach for this first when someone reports a project missing from their Projects list or cannot log time against it: an empty roster, or a roster without them in it, IS the explanation — visibility is membership. It is also the id source for projectMembers_update and projectMembers_delete. Note the same person can appear several times on one project, once per position.

project any
employee any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
projectMembers_update write

Change an existing membership's position by id (employee|tech-lead|account-manager|viewer) — get the id from projectMembers_list or the projectMembers array on projects_get. Use this to promote or demote IN PLACE; use projectMembers_create to add a second, additional position alongside the one already held. Changing a position can REVOKE sight of the project for someone whose role is project-scoped (a ROLE_PROJECTS_LEAD demoted from tech-lead to employee stops seeing it). Cannot move a membership to another person or project — delete and recreate for that. Requires ROLE_PROJECTS_MANAGER. Write.

position string
positionName string
id* string | integer
projectTemplates_create write

Create a reusable project blueprint from a structure document (version, project, phases, and their lists/tasks). Offsets inside it are RELATIVE — startOffsetDays and durationDays are counted in days from the startDate given at instantiate time, so one template serves every future start. The project.name in the structure is a placeholder; override it per customer when instantiating. The structure is validated server-side against the schema for its declared version, and a violation names the offending JSON pointer. Write.

name* string
structure object
The blueprint document. Server-validated against the project-template JSON Schema for its declared version. Offsets are in days and resolve against the startDate passed to projectTemplates_instantiate.
projectTemplates_delete write

Delete a project template by id. Soft delete, and it does NOT touch projects already created from the template — those are ordinary projects and live on. Write.

id* string | integer
projectTemplates_get read

Get one project template by id, including its full structure document. projectTemplates_list finds the id. Read this before projectTemplates_update — the structure is written WHOLE, so an update must send the complete document, not a fragment. Read-only.

id* string | integer
projectTemplates_instantiate read

Build a real project from a template — the project, its phases, its task lists and every task, in ONE atomic call. startDate is required and is the anchor every startOffsetDays in the template resolves against. Pass name to override the template's placeholder project name, and client to attach the new project to a customer: instantiating twice against the SAME client is how one customer ends up holding several engagements, each its own project. Returns the created project. Write.

startDate* string | null
name string | null
client string | null
id* string | integer
projectTemplates_list read

List the org's project templates — reusable blueprints of a project, its phases, its task lists and its tasks. Reach for this BEFORE projects_create when the same shape of project is set up repeatedly (an engagement type, an audit, an onboarding): instantiating a template builds the whole tree in one call, where projects_create makes an empty project you then have to fill by hand. The row flagged isDefault is the org's built-in template, applied to a project created with no template chosen. Read-only.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
projectTemplates_update write

Update a project template by id. The structure column is stored and replaced WHOLE, never merged — send the complete document or the parts you omit are gone. Read the current one with projectTemplates_get first. Changing a template does NOT touch projects already instantiated from it; there is no back-propagation. Write.

name string
structure object
The blueprint document. Server-validated against the project-template JSON Schema for its declared version. Offsets are in days and resolve against the startDate passed to projectTemplates_instantiate.
id* string | integer
resourceRequestCandidates_get read

One recruitment candidate by id. The id comes from resourceRequestCandidates_list. Needs ROLE_HR_MANAGER. Read-only.

id* string | integer
resourceRequestCandidates_list read

The candidates put forward against hiring requests — people in a recruitment pipeline, not employees available for allocation. Filter by the request id from resourceRequests_list. Needs ROLE_HR_MANAGER. Read-only.

resourceRequest any
candidate any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
resourceRequests_get read

One hiring request by id, with its position and status. Get the id from resourceRequests_list. HR/recruitment, not resourcing allocation. Needs ROLE_HR_MANAGER. Read-only.

id* string | integer
resourceRequests_list read

Open hiring requests — a request to recruit for a position, in the HR domain. Despite the name this is NOT resourcing allocation demand: it is recruitment. Returns the collection; resourceRequests_get reads one, and resourceRequestCandidates_list gives the people put forward for it. Needs ROLE_HR_MANAGER. Read-only.

status any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
resourcing_importTimeline read

Import a resourcing allocation timeline sheet (fetch it via the Drive MCP, pass its CSV verbatim). This is a FULL-REPLACE mirror of the org's Allocation rows for `year`: rows in the sheet are created/updated, and any existing row for that year absent from the sheet is DELETED — not a merge. DRY-RUN BY DEFAULT: an omitted dryRun previews and writes nothing; pass dryRun:false to apply. The report gives `created` / `replaced` plus `unmatchedPeople` / `unmatchedProjects`. TWO THINGS ARE EASY TO MISS: a sheet row whose project does not resolve is SKIPPED while the call still reports success, so a green result can hide a partial import; and a role code the position catalogue does not already hold is CREATED as a new position rather than rejected — see `createdPositions`. Both are called out in `warnings` when they happen; surface that to the user rather than reporting only `created`. A sheet that parses to zero rows is refused (it looks exactly like a bad read about to wipe the whole timeline) unless you pass force:true. Read allocations_list afterwards to see what landed. High-impact. Write.

csv* string
The allocation-timeline sheet as CSV (fetch it via the Drive MCP). A role code the position catalogue does not already hold is CREATED as a new position rather than rejected — check `createdPositions` and `warnings` in the report.
dryRun boolean
Preview only; report what WOULD change and write nothing. Default true.
force boolean
Allow an apply whose parse produced zero rows (would otherwise refuse to wipe).
year number
Planning year the day columns fall in.
resourcingActuals_get read

Reported hours vs the plan, per person per week, over a from/to window — the 'is the team actually on plan?' question, which NO other resourcing tool answers: allocations tell you what was PLANNED, this tells you what was DELIVERED. Returns week columns plus one row per person (planned %, reported %, variance, totals, and a per-project breakdown). reportedPercent null means 'no contract that week' and 0 means 'a contract existed and nothing was reported' — do NOT collapse the two. Pass financials for revenue/cost/margin, which are omitted otherwise. Needs the resourcing module and ROLE_RESOURCING_MANAGER. Read-only.

from* string
Window start, YYYY-MM-DD. Required.
to* string
Window end, YYYY-MM-DD, INCLUSIVE. Required. Must be on or after `from` (the backend 422s otherwise).
financials boolean
Include revenue / cost / margin per person. Omitted, those fields come back null and no financial pass runs — ask for it only when the question is about money. Asking does not guarantee an answer: the figures also require ROLE_RESOURCING_FINANCIALS_VIEWER, which ROLE_RESOURCING_MANAGER alone does not carry (per-person cost is derived from pay). Without it the call still succeeds — the money fields come back null exactly as if unasked, so treat null as withheld-or-unrated, never as zero.
resourcingBench_get read

Who is NOT staffed over a from/to window — the bench. Reach for it when asked who to put on a new project or where capacity is going unused; resourcingActuals_get tells you how loaded people are, this tells you who has no load at all. IT DOES NOT KNOW ABOUT LEAVE: freePercent is 100 minus confirmed allocations, nothing else, so someone on three weeks' approved holiday reads 100% free and no field on the response says otherwise. Answering 'who is available' from this alone will put people on projects while they are away — cross-check holidays_active or holidays_list. Needs the resourcing module. Read-only.

from* string
Window start, YYYY-MM-DD. Required.
to* string
Window end, YYYY-MM-DD, INCLUSIVE. Required. Must be on or after `from` (the backend 422s otherwise).
resourcingRequests_list read

Open resourcing requests — someone asking for a person to be allocated to a project, which is the demand side of resourcing. This is the flow the Resourcing UI's Requests view renders. Do NOT confuse it with resourceRequests_list: that one is HR RECRUITMENT (hiring for a position). Pair it with resourcingRequestsHistory_list for what has already been decided, and resourcingBench_get for who could satisfy a request. Needs the resourcing module and ROLE_RESOURCING_MANAGER. Read-only.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
resourcingRequestsHistory_list read

What has already happened to resourcing requests — the decision trail (confirmed, declined, changed) behind the open requests in resourcingRequests_list. Reach for it to answer 'was this already asked for and turned down?' before proposing the same allocation again. Needs the resourcing module and ROLE_RESOURCING_MANAGER. Read-only.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
resourcingSchedule_get read

The planned resourcing schedule over a from/to window — the allocation timeline as the planner shows it. Use it for what is BOOKED going forward; use resourcingActuals_get for what was actually reported against it. Needs the resourcing module and ROLE_RESOURCING_MANAGER. Read-only.

from* string
Window start, YYYY-MM-DD. Required.
to* string
Window end, YYYY-MM-DD, INCLUSIVE. Required. Must be on or after `from` (the backend 422s otherwise).
responsibilities_create write

Create a responsibility inside a group (responsibilityGroup = group id or IRI, + name, required; optional description; optional parent = another responsibility IRI for nesting). Assign people to it via responsibilityEmployees_create. Write.

parent any
responsibilityGroup* string
name* string
description string
children array
responsibilities_get read

Get one responsibility by id.

id* string | integer
responsibilities_list read

List responsibilities inside a RACI group. Filter by responsibilityGroup. Responsibilities can nest via parent; people are assigned to them through responsibilityEmployees, not directly.

responsibilityGroup any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
responsibilities_update write

Update a responsibility by id (name, description, parent, responsibilityGroup = group id or IRI). Write.

parent any
responsibilityGroup string
name string
description string
children array
id* string | integer
responsibilityEmployees_create write

Assign an employee to a responsibility (responsibility = responsibility id or IRI, employee = employee id or IRI, percentage 0-100, all required; optional targets and description). Write.

responsibility* string | null
employee* string | null
percentage* integer
targets string
description string
responsibilityEmployees_delete write

Remove an employee's assignment from a responsibility by id. Write.

id* string | integer
responsibilityEmployees_get read

Get one responsibility assignment by id.

id* string | integer
responsibilityEmployees_list read

List who is assigned to which responsibility, and at what percentage. Filter by employee to read one person's entire RACI load across every group.

employee any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
responsibilityEmployees_update write

Update a responsibility assignment by id (percentage, targets, description). Write.

responsibility string | null
employee string | null
percentage integer
targets string
description string
id* string | integer
responsibilityGroups_create write

Create a responsibility group / RACI area (name required; optional description and responsibleEmployee = the accountable person, given as a plain employee id like 6 (from people_list) or the /people/6 IRI). This is the top-level 'Odpowiedzialności' item. Add individual responsibilities under it via responsibilities_create. Write.

responsibleEmployee* string | null
name* string
description string
responsibilities array
responsibilityGroups_get read

Get one responsibility group by id.

id* string | integer
responsibilityGroups_list read

List responsibility groups / RACI areas — the top-level "Odpowiedzialności" items, each with an accountable person. Individual responsibilities hang underneath them.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
responsibilityGroups_update write

Update a responsibility group by id (name, description, responsibleEmployee = employee id or IRI). Write.

responsibleEmployee string | null
name string
description string
responsibilities array
id* string | integer
scheduleEmployees_get read

One schedule-to-employee assignment by id. The id comes from scheduleEmployees_list. Needs ROLE_SCHEDULES_MANAGER. Read-only.

id* string | integer
scheduleEmployees_list read

Which employees are assigned to which working-time schedules. Use it to go from a schedule (schedules_list) to its people, or to find the schedule a given employee follows. Needs ROLE_SCHEDULES_MANAGER. Read-only.

employee any
dateFrom.before string
dateFrom.strictly_before string
dateFrom.after string
dateFrom.strictly_after string
dateTo.before string
dateTo.strictly_before string
dateTo.after string
dateTo.strictly_after string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
schedulePlan_list read

The schedules in force on ONE given date — pass the date in the path. Reach for it to answer 'who is working today / on this date' without reading every schedule and resolving its ranges yourself. Unlike the other schedule reads this only needs ROLE_USER, so it is the one available to an ordinary employee. Read-only.

current boolean
Filter only newest Schedules, without a successor
date* string | integer
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
scheduleRanges_get read

One schedule time range by id. The id comes from scheduleRanges_list. Needs ROLE_SCHEDULES_MANAGER. Read-only.

id* string | integer
scheduleRanges_list read

The time ranges that make up working-time schedules — the actual hours a schedule covers. Read the parent with schedules_get first; this expands its ranges. Needs ROLE_SCHEDULES_MANAGER. Read-only.

schedule any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
schedules_get read

One working-time schedule by id, with its ranges and assigned employees. The id comes from schedules_list; scheduleRanges_list and scheduleEmployees_list read its parts. Needs ROLE_SCHEDULES_MANAGER. Read-only.

id* string | integer
schedules_list read

Working-time schedules — the shift/working patterns an org defines, NOT project allocation. Use resourcingSchedule_get for who is booked on what; use this for the working patterns themselves. schedules_get reads one by id. Needs ROLE_SCHEDULES_MANAGER. Read-only.

current boolean
Filter only newest Schedules, without a successor
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
stages_get read

Get one deal stage by id.

id* string | integer
stages_list read

List deal stages, in order. Filter by pipeline. deals_create requires a stage id from here, and moving a deal between stages is what dealStageHistories records.

pipeline any
order.position string
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
storage_createUploadTicket read

Mint a short-lived, single-use ticket for attaching a LARGE file to any record — the way asset PICTURES actually get in, since an image is always past the base64 ceiling. On property/location/invoices/transaction-attachments the newest upload becomes the record's visible image, replacing the previous one; on clients/agreements/candidates uploads accumulate. Use this instead of storage_upload whenever the file is more than a few tens of KB: that tool carries the bytes as base64, which a caller has to emit as text, and a 400 KB JPEG becomes ~533 K base64 characters, far beyond what fits in one response. Pass relationName + relationId plus a filename; you get back an uploadUrl and a ready-to-run curl. Then send the file's RAW BYTES to that URL (curl --data-binary @photo.jpg) — not base64, not multipart — and the response carries the created Storage record. The ticket expires in 15 minutes, works once, and can only file against the one record it names. Write.

relationName* string
Which kind of record the file is filed against. property = an asset (assets_list; the backend calls them properties, hence the singular name) — and this is what sets the asset's PICTURE; projects = a project (projects_list); project-tasks = a task (tasks_list); clients = a client (clients_list); contractors = a cost/contractor record; contracts = a contract — but prefer contractAttachments_create/createUploadTicket, which also clears contracts.problem_missing_document; location = a location (locations.*); employees / candidates / candidate-cvs / candidate-cover-letters = HR records; invoices / agreements / bank-account-statements / transaction-attachments / transaction-attachment-documents = finance documents.
relationId* string | number
The id of that record — as returned by its list tool. A full IRI (/assets/7, /projects/12) is accepted and reduced to the bare id.
filename* string
Filename to record on the upload, e.g. elewacja-frontowa.jpg.
contentType string
MIME type of the file. If omitted, the content-type header sent with the bytes is used — and it must still land inside this allowlist. image/webp and image/svg+xml are NOT accepted (the org logo route takes them, this one does not); convert to JPEG or PNG first. A refusal arrives only AFTER the bytes are sent, as a 422, with the ticket already consumed — so mint a fresh ticket to retry.
storage_upload read

Attach a file to any record Flowtly's generic storage accepts — an ASSET (relationName "property"), a project, a task, a client, a location, a contractor, an invoice, an HR record. This is the only route to an asset PICTURE: uploading with relationName "property" sets the image the app shows for that asset (served as `file` on the asset payload). Property has no image column -- the picture is derived from this table at read time, which is why nothing on the entity hints it exists. It is ONE slot and the newest upload wins, so a second image replaces the first rather than adding to a gallery; both rows stay listed under /assets/{id}/documents. The same holds for location, invoices and transaction-attachments; clients, agreements and candidates instead accumulate every upload under `files`; the rest surface only on their /documents route. `file` is omitted from LIST responses unless the request passes ?include=file, so read one record back to confirm the picture landed. Pass relationName + relationId (the id from that record's list tool; a /assets/7 IRI is accepted and reduced) plus the bytes as base64 with a contentType and filename. SIZE LIMIT: the bytes travel as base64 inside this call, so keep it under roughly 150 KB — photographs are almost always past that, and for those use storage_createUploadTicket, which has no ceiling. Permissions are whatever editing the OWNING record needs: the backend resolves relationName to that entity and asks its own voter, so filing against an asset needs the assets permission, against a client the clients one. For a contract, prefer contractAttachments_create instead — it also clears contracts.problem_missing_document, which this does not. Write.

relationName* string
Which kind of record the file is filed against. property = an asset (assets_list; the backend calls them properties, hence the singular name) — and this is what sets the asset's PICTURE; projects = a project (projects_list); project-tasks = a task (tasks_list); clients = a client (clients_list); contractors = a cost/contractor record; contracts = a contract — but prefer contractAttachments_create/createUploadTicket, which also clears contracts.problem_missing_document; location = a location (locations.*); employees / candidates / candidate-cvs / candidate-cover-letters = HR records; invoices / agreements / bank-account-statements / transaction-attachments / transaction-attachment-documents = finance documents.
relationId* string | number
The id of that record — as returned by its list tool. A full IRI (/assets/7, /projects/12) is accepted and reduced to the bare id.
bytesBase64* string
The file's bytes, base64-encoded. Practical ceiling is about 150 KB of file: base64 inflates it by 4/3 and the result has to be emitted inside this call, so a larger file cannot be sent this way at all — no error arrives, the call simply cannot be written. Use storage_createUploadTicket for those, which sends the bytes straight from disk and has no such ceiling. Photographs are almost always past it.
contentType* string
MIME type of the bytes. The backend allowlist is exactly these — notably image/webp and image/svg+xml are NOT accepted here (the org logo route takes them, this one does not), and a refusal is a bare 422 "file: Please upload a valid file" that names no type. Convert a webp to JPEG or PNG before uploading.
filename* string
Filename to record on the upload, e.g. elewacja-frontowa.jpg.
tagDefinitions_create write

Create a tag definition (name, level, tagGroup required) within a tag group. When the group's allowedRelations contains "project", each definition here IS a project folder — this is the tool that creates one. Write.

name* string
level* integer
label string
tagGroup* string
tagDefinitions_list read

List tag definitions — the tags that can be attached to records, each inside a tag group. tags_create takes a tagDefinition id from here plus the record to attach it to.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
tagGroups_create write

Create a tag group (name required) to organize related tag definitions. Also how you create a PROJECT FOLDER container: pass allowedRelations: ["project"] and the group's definitions become folders in the projects list. A group with empty allowedRelations is universal and is NOT treated as a folder. Write.

name* string
allowedRelations array | null
tagGroups_list read

List tag groups — the containers that organize tag definitions.

cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
tags_create write

Attach a tag definition to a record (tagDefinition + relationName + relationId; e.g. relationName "counterparty" to tag a supplier/vendor). Use relationName "project" to PUT A PROJECT IN A FOLDER, where tagDefinition is the folder. Only root projects (no parent) are grouped into folders — phases follow their parent, so file the root and the tree follows. Write.

tagDefinition* string
relationName string
relationId* string
weight string
taxRules_create write

Create a tax rule. Write.

taxGroup* string
country* string
taxAmount* string
taxRules_list read

List tax rules — the rates and the periods they apply to. Filter by taxGroup.

taxGroup any
cursor string
Opaque pagination token from a previous response's nextCursor. Omit for the first page. When present, other filters are ignored — the cursor already encodes them.
itemsPerPage integer
Page size (max 100). Ignored when cursor is set.
taxRules_update write

Update a tax rule by id. Write.

taxGroup string
country string
taxAmount string
id* string | integer