Flowyer API reference
API version: 90bdda12e80e
The complete Flowyer REST API for developers: endpoints, parameters, responses and code samples.
With the Flowyer API your own software or an AI assistant can read, create and modify records (matters, clients, tasks, work hours, costs and documents) on behalf of one Flowyer user. Every request runs with exactly that user's permissions, narrowed further by the scopes of the key or grant. Nothing can be deleted through the API.
Endpoint and field descriptions are in English, because AI assistants read the very same text.
Base URL
Every endpoint lives under the addresses below. Always use the exact https host starting with www., because a redirect would drop the Authorization header.
Base URL
https://www.flowyer.com/api/agent
https://www.flowyer.hu/api/agent
https://www.flowyer.ro/api/agent
https://www.flowyer.de/api/agent
https://www.flowyer.it/api/agent
Authentication
Every request needs a Bearer token in the Authorization header. There are two kinds of token; the endpoints treat both the same way.
Personal API key
The user creates it in Flowyer under Settings > API keys, once an office administrator has enabled the API. A key starts with flw_, is shown only once and can be revoked at any time with immediate effect.
OAuth 2.1 for connected applications
An integration meant for other users asks for access with the OAuth 2.1 Authorization Code + PKCE (S256) flow instead of a key; clients can register dynamically. An access token is valid for 60 minutes, a refresh token for 60 days, and the pair is replaced on every use. The PKCE code_verifier is 43 to 128 characters of A-Z, a-z, 0-9 and - . _ ~ (RFC 7636 4.1), that is base64url without padding: a trailing "=" is refused.
- Server metadata
https://www.flowyer.com/.well-known/oauth-authorization-server- Authorization
https://www.flowyer.com/authorize- Token
https://www.flowyer.com/api/agent/oauth/token- Client registration
https://www.flowyer.com/api/agent/oauth/register- Revocation
https://www.flowyer.com/api/agent/oauth/revoke
Request samples
curl 'https://www.flowyer.com/api/agent/me' \
-H 'Authorization: Bearer flw_...'
Scopes
Every endpoint except GET /me requires one scope. When the key or grant lacks it, the answer is 403 scope_missing. GET /me needs no scope: use it to see what the credential may do.
| Scope | Meaning | Endpoints |
|---|---|---|
matters:read |
Read matters | GETList matters GETResolve matter by name GETGet matter GETList matter responsibles |
matters:create |
Create matters | POSTCreate matter |
matters:update |
Update matters | PATCHUpdate matter POSTAdd matter responsible |
clients:read |
Read clients | GETList clients GETResolve client by name GETGet client |
clients:create |
Create clients | POSTCreate client |
clients:update |
Update clients | PATCHUpdate client |
tasks:read |
Read tasks | GETList tasks GETGet task |
tasks:create |
Create tasks | POSTCreate task |
tasks:update |
Update tasks | PATCHUpdate task POSTClose task |
workhours:read |
Read time entries | GETList time entries GETGet time entry |
workhours:create |
Record time entries | POSTRecord time entry |
workhours:update |
Update time entries | PATCHUpdate time entry |
costs:read |
Read costs | GETList costs GETGet cost |
costs:create |
Record costs | POSTRecord cost |
costs:update |
Update costs | PATCHUpdate cost |
invoices:read |
Read invoices | GETList invoices GETGet invoice |
documents:read |
List documents (metadata only, no download) | GETList matter documents GETList matter folders |
documents:download |
Download documents (full contents) | GETDownload document |
documents:create |
Upload documents | POSTCreate matter folder POSTStart document upload POSTFinish document upload |
documents:update |
Rename documents | PATCHRename document PATCHRename matter folder |
colleagues:read |
List colleagues (names only) | GETList colleagues |
knowledge:read |
Read knowledge base | GETList knowledge base articles GETGet knowledge base article GETList knowledge base categories GETGet knowledge base category |
knowledge:create |
Create knowledge base entries | POSTCreate knowledge base article POSTCreate knowledge base category |
knowledge:update |
Update knowledge base entries | PATCHUpdate knowledge base article PATCHUpdate knowledge base category |
emails:read |
Read case emails | GETList matter emails GETGet email |
litigation:read |
List litigation history | GETList matter litigation history |
litigation:create |
Create litigation history entries | POSTCreate litigation history entry |
litigation:update |
Update litigation history entries | PATCHUpdate litigation history entry |
mail_register:read |
Read mail register | GETList mail register GETGet mail register entry |
mail_register:create |
Create mail register entries | POSTCreate mail register entry |
mail_register:update |
Update mail register entries | PATCHUpdate mail register entry |
comments:read |
Read case comments | GETList matter comments |
comments:create |
Post case comments | POSTPost matter comment |
comments:update |
Edit own case comments | PATCHEdit own matter comment |
trials:read |
Read hearings | GETList matter hearings GETGet hearing |
trials:create |
Create hearings | POSTCreate hearing |
trials:update |
Update hearings | PATCHUpdate hearing |
courts:read |
List courts | GETList courts |
hr:read |
Read who is on leave (dates only) | GETList staff leaves |
calendar:read |
Read calendar events | GETList calendar events GETGet calendar event |
calendar:create |
Create calendar events | POSTCreate calendar event |
calendar:update |
Update calendar events | PATCHUpdate calendar event |
bookings:read |
View appointment bookings | GETList bookings GETGet booking |
bookings:create |
Create appointment bookings (emails the client) | POSTCreate booking |
bookings:update |
Reschedule and edit bookings | PATCHUpdate booking |
bookings:send |
Confirm or cancel bookings (emails the client) | POSTConfirm booking POSTCancel booking |
tickets:read |
Read support tickets | GETList tickets GETGet ticket |
tickets:reply |
Reply to tickets (emails the client) | POSTReply to ticket |
tickets:update |
Change ticket status | PATCHChange ticket status |
portal:read |
Client portal: read threads and messages | GETList client portals GETList portal messages |
portal:reply |
Client portal: reply to the client (posts a message and emails them) | POSTReply in client portal |
Errors
Every error has the same shape: a machine code (error), an English message (message), the message in the office language (message_localized) and optional details. Retrying an unchanged request makes no sense on 401, 403, 404 and 422, with two exceptions: on 404 upload_not_finished, finish the upload to upload_url first, then call complete again; on an expired OAuth access token (401 expired_key), refresh the token first.
| HTTP | error | Meaning |
|---|---|---|
| 400 | invalid_json use_www_host bad_request |
Malformed request: invalid JSON body, wrong host (use the www. host) or an unreadable request. |
| 401 | missing_key invalid_key revoked_key expired_key user_inactive office_not_found |
The key is missing, invalid, revoked or expired, its owner is no longer active, or the office of the key cannot be found. |
| 403 | scope_missing module_forbidden forbidden office_api_disabled licence_expired office_archived |
A permission is missing on the key or the user, or the office has not enabled the API. |
| 404 | not_found matter_not_visible unknown_endpoint upload_not_finished replay_not_visible |
The record does not exist or the user may not see it; unknown endpoint; the uploaded file has not reached storage yet (upload_not_finished: upload it to upload_url, then call complete again); or a retried create whose record was created earlier but has since been deleted or hidden (replay_not_visible: nothing new was created, do not create it again). |
| 405 | delete_not_allowed update_not_allowed method_not_allowed |
Deleting is not possible through the API; modifying only where the description offers a PATCH operation. |
| 409 | client_exists client_exists_generic idempotency_key_reused duplicate_in_progress record_conflict record_locked duplicate_name |
A client with that name already exists; a document with that name already exists in the folder (duplicate_name); the record changed since you read it (record_conflict: read it again and resend the change with the new updated_at); the record has been invoiced or locked (record_locked: make the change in Flowyer); the Idempotency-Key was used on another resource; or an identical request is being processed. |
| 413 | request_too_large file_too_large |
The request body is too large (request_too_large), or the file to upload exceeds the limit (file_too_large; details.max gives the limit in bytes). |
| 422 | validation_failed unknown_client unknown_colleague blocked_file_type upload_token_invalid folder_not_in_matter document_name_too_long |
An invalid or missing field (details.fields shows which); an invalid, used or expired upload token (upload_token_invalid: start the upload again); or a folder that does not belong to the matter (folder_not_in_matter). |
| 429 | rate_limited auth_rate_limited |
Too many requests (300 per minute per key); the Retry-After header says when to retry. |
| 500 | server_error |
Unexpected error. The request was logged under its request_id; retry later, and if it repeats, quote that id to Flowyer support. |
| 503 | office_maintenance office_setup_incomplete temporarily_unavailable |
The office is temporarily unavailable (maintenance or setup in progress); the Retry-After header says when to retry. |
Response samples · 403
{
"error": "scope_missing",
"message": "This API key does not have the \"clients:create\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"clients:create\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "clients:create"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Pagination
Record lists are paginated with the limit and offset parameters (default 25, at most 100 items per page). pagination.total is the full number of matches; the next page's offset is the previous offset plus limit. GET /colleagues, GET /clients/resolve and GET /matters/resolve are not paginated: they accept only limit (with their own default and maximum, see the endpoint) and answer total and truncated inside data instead of a pagination object, so do not send them an offset.
Response samples · 200
{
"data": [
{
"id": 1234,
"...": "..."
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 134
}
}
Safe retries
Every create accepts an Idempotency-Key header (or an idempotency_key field). A request repeated with the same value returns the record created the first time (200, idempotent_replay) instead of creating another. Different keys always create different records. Without a key, an identical create within 120 seconds returns the first record.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/clients' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{"name": "Kovács Kft."}'
Updates and conflicts
An update is a PATCH on the record's own path: only the fields you send change, and an explicit null clears a field where the field description allows it; for some fields null keeps the stored value, so read the field description. Send the updated_at value of your last read in expected_updated_at. If someone else changed the record in the meantime, the answer is 409 record_conflict; read the record again before retrying.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/matters/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{"expected_updated_at": "2026-10-30T09:15:00+01:00", "description": null}'
Dates and time zone
Dates are YYYY-MM-DD, timestamps are ISO 8601 with the offset of the office time zone. The office time zone is in the office.timezone field of GET /me.
Limits
At most 300 requests per minute per key or token; beyond that the answer is 429 and the Retry-After header says when to retry. A JSON request body may be at most 64 KB, an uploaded document at most 100 MB.
Versions
The version of the OpenAPI description changes with every change and is also sent as the ETag header, so machine clients can check with If-None-Match whether it changed.
Me
Who am I
/api/agent/me
Returns the user and office this API key acts for, the key's scopes (what it may do) and the rate limits. Call it first to learn which operations are available; needs no scope.
This endpoint takes no input.
Responses
200
OK.
datarequiredFields of data
userFields of user
idnamelanguageofficeFields of office
idnametimezoneIANA zone the API reads and writes timestamps in, e.g. Europe/Budapest.
currency_idThe office currency (Flowyer currency id): costs.create books in it when currency_id is omitted.
currencyISO code of the office currency, e.g. HUF, EUR, RON.
keyFields of key
idnameName the user gave this key, or the connected app for an OAuth token.
kind"key" = a static API key, "oauth" = an OAuth 2.1 access token.
"key" "oauth" prefixFirst characters of a static key, for identification only; null for OAuth.
expires_atlast_used_atscopesScopes of this credential, e.g. "matters:read". An operation whose scope is missing answers 403.
available_scopesAll scopes the key owner could enable in Flowyer.
limitsFields of limits
requests_per_minutelist_max_limitchannelHow the request reached Flowyer.
"rest" "mcp"
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Unknown query parameter (this operation takes none); details.fields names it.
details.fields names it.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/me' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/me", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/me",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"user": {
"id": 1234,
"name": "string",
"language": "string"
},
"office": {
"id": 1234,
"name": "string",
"timezone": "string",
"currency_id": 1234,
"currency": "string"
},
"key": {
"id": 1234,
"name": "string",
"kind": "key",
"prefix": "string",
"expires_at": "2026-10-30T09:15:00+01:00",
"last_used_at": "2026-10-30T09:15:00+01:00"
},
"scopes": [
"string"
],
"available_scopes": [
"string"
],
"limits": {
"requests_per_minute": 0,
"list_max_limit": 0
},
"channel": "rest"
}
}
{
"error": "invalid_key",
"message": "The API key is not valid.",
"message_localized": "The API key is not valid.",
"locale": "en",
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Colleagues
List colleagues
/api/agent/colleagues
Lists the active colleagues of the office (id, name) for assigned_to_user_id / responsible_user_id. With q (a name as heard; accents, casing and small typos tolerated) answers status exact/similar/none like the resolve tools: only "exact" is safe to use directly.
Query parameters
qA colleague's name to look up, as heard or dictated, in any word order. Omit it to list every active colleague.
"Nagy Péter"limitMaximum colleagues to return (default 200).
200Responses
200
OK.
datarequiredFields of data
statusOnly with q: exact = one confident match (safe to use its id), similar = approximate or several matches (confirm with the user first), none = no match (ask the user). Null without q.
"exact" "similar" "none" colleaguesActive colleagues, best match first with q, otherwise by name.
Fields of colleagues
idUser id: pass it as assigned_to_user_id / responsible_user_id.
nameDisplay name as Flowyer shows it (with the title, if any).
titleName prefix such as "Dr.", or null.
totalHow many colleagues matched (all active colleagues without q) before the list was cut to limit.
truncatedTrue when more colleagues matched than were returned: narrow q or raise limit.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/colleagues?q=Nagy%20P%C3%A9ter' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/colleagues?q=Nagy%20P%C3%A9ter", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/colleagues?q=Nagy%20P%C3%A9ter",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"status": "exact",
"colleagues": [
{
"id": 1234,
"name": "string",
"title": "string"
}
],
"total": 1,
"truncated": true
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"colleagues:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"colleagues:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "colleagues:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Matters
List matters
/api/agent/matters
Lists the matters (cases) the user may see, newest first. Use it to find a matter by case number, subject or client before referring to it elsewhere. Archived matters are hidden unless include_archived is true.
Query parameters
qFree-text search in the subject and the client names (accents and case ignored) and the office's other case numbers; or a case number, either exactly as returned in case_number (the office numbering scheme) or as year/sequence such as "2026/0025".
"2026/0025"client_idOnly matters of this client.
512responsible_user_idOnly matters this user (ids from colleagues.list) is responsible for: as the main responsible, an additional responsible, or a member of a responsible workgroup (as the web colleague filter).
updated_sinceOnly matters changed at or after this time (ISO 8601).
"2026-10-01T00:00:00+02:00"include_archivedInclude archived matters.
falselimitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idMatter id. Use it as matter_id elsewhere.
1234case_numberCase number as displayed in Flowyer, in the office numbering scheme.
"2026/0025"subject"Kovács Kft. v. Nagy Bt. - contract dispute"status"active" "archived" case_type_idcase_typeLocalized name of the case type.
responsible_user_idresponsibleDisplay name of the responsible lawyer.
clientsClients (principals) of the matter.
Fields of clients
idnamestarted_onrestrictedTrue when only the responsible lawyers may see the matter (Chinese wall).
created_atupdated_atcreated_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/matters?q=2026%2F0025&client_id=512&updated_since=2026-10-01T00%3A00%3A00%2B02%3A00' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/matters?q=2026%2F0025&client_id=512&updated_since=2026-10-01T00%3A00%3A00%2B02%3A00", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/matters?q=2026%2F0025&client_id=512&updated_since=2026-10-01T00%3A00%3A00%2B02%3A00",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 1234,
"case_number": "2026/0025",
"subject": "Kovács Kft. v. Nagy Bt. - contract dispute",
"status": "active",
"case_type_id": 1234,
"case_type": "string",
"responsible_user_id": 1234,
"responsible": "string",
"clients": [
{
"id": 1234,
"name": "string"
}
],
"started_on": "2026-10-30",
"restricted": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"matters:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"matters:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "matters:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Resolve matter by name
/api/agent/matters/resolve
Finds a matter by a spoken or misspelled subject or client name (accents, casing, small typos tolerated). Returns status exact/similar/none and candidates. Only "exact" is safe to use directly; else confirm with the user. Many lookalikes are never "exact": narrow with client_id or a fuller name.
Query parameters
namerequiredMatter subject or client name to look up, as heard or dictated.
"ingatlan adásvétel"client_idOnly matters of this client (use after clients.resolve returned an exact client).
limitMaximum candidates to return (default 5).
5Responses
200
OK.
datarequiredFields of data
statusexact = one confident match (safe to use its id). similar = one or more approximate matches, confirm with the user first. none = no match, ask the user.
"exact" "similar" "none" mattersCandidate matters, best match first.
Fields of matters
idMatter id. Use it as matter_id elsewhere.
1234case_numberCase number as displayed in Flowyer, in the office numbering scheme.
"2026/0025"subject"Kovács Kft. v. Nagy Bt. - contract dispute"status"active" "archived" case_type_idcase_typeLocalized name of the case type.
responsible_user_idresponsibleDisplay name of the responsible lawyer.
clientsClients (principals) of the matter.
Fields of clients
idnamestarted_onrestrictedTrue when only the responsible lawyers may see the matter (Chinese wall).
created_atupdated_atcreated_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserattotalHow many matters matched before the list was cut to limit.
truncatedTrue when more matters matched than were returned, or too many lookalikes existed to check them all; narrow with client_id or a fuller name, or raise limit, rather than choosing from an incomplete list.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/matters/resolve?name=ingatlan%20ad%C3%A1sv%C3%A9tel' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/matters/resolve?name=ingatlan%20ad%C3%A1sv%C3%A9tel", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/matters/resolve?name=ingatlan%20ad%C3%A1sv%C3%A9tel",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"status": "exact",
"matters": [
{
"id": 1234,
"case_number": "2026/0025",
"subject": "Kovács Kft. v. Nagy Bt. - contract dispute",
"status": "active",
"case_type_id": 1234,
"case_type": "string",
"responsible_user_id": 1234,
"responsible": "string",
"clients": [
{
"id": 1234,
"name": "string"
}
],
"started_on": "2026-10-30",
"restricted": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
],
"total": 1,
"truncated": true
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"matters:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"matters:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "matters:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get matter
/api/agent/matters/{id}
Returns one matter (case) with its clients and details. Answers 404 when the matter does not exist or the user may not see it.
Path parameters
idrequiredMatter id.
1234Responses
200
OK.
datarequiredFields of data
idMatter id. Use it as matter_id elsewhere.
1234case_numberCase number as displayed in Flowyer, in the office numbering scheme.
"2026/0025"subject"Kovács Kft. v. Nagy Bt. - contract dispute"status"active" "archived" case_type_idcase_typeLocalized name of the case type.
responsible_user_idresponsibleDisplay name of the responsible lawyer.
clientsClients (principals) of the matter.
Fields of clients
idnamestarted_onrestrictedTrue when only the responsible lawyers may see the matter (Chinese wall).
created_atupdated_atcreated_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratdescriptionlitigiousarchived_atDay the matter was archived, YYYY-MM-DD. Flowyer records the date only, no time of day.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/matters/1234' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/matters/1234",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 1234,
"case_number": "2026/0025",
"subject": "Kovács Kft. v. Nagy Bt. - contract dispute",
"status": "active",
"case_type_id": 1234,
"case_type": "string",
"responsible_user_id": 1234,
"responsible": "string",
"clients": [
{
"id": 1234,
"name": "string"
}
],
"started_on": "2026-10-30",
"restricted": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
},
"description": "string",
"litigious": true,
"archived_at": "2026-10-30"
}
}
{
"error": "matter_not_visible",
"message": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"message_localized": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"matter_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Create matter
/api/agent/matters
Opens a matter (case) with the next case number of the year. Needs at least one existing client (find or create it first). The responsible lawyer defaults to the key's user and is notified otherwise. Parties, contacts and fees are set in Flowyer.
Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
subjectrequiredShort title of the matter as it appears in lists. Plain text; "<" and ">" are not allowed.
^[^<>]*$"Kovács Kft. v. Nagy Bt. - contract dispute"case_type_idrequiredCase type (area of law) id from the global Flowyer case-type list: pick the one whose name matches the user's words, or ask the user to choose by name. An unknown id answers 422 with details.known_case_types (id + localized name); an existing matter shows its type in matters.get case_type_id / case_type.
client_idsrequiredIds of the clients (principals). At least one, at most 20.
[
512
]responsible_user_idResponsible lawyer: a user id from colleagues.list (look a dictated name up there). Default: the user behind the key.
started_onStart date, YYYY-MM-DD. Default: today.
"2026-10-29"descriptionLonger description, plain text.
main_case_numberThe office's own main reference number of the matter. Required when the office numbering scheme uses it (the API answers 422 otherwise).
restrictedtrue = only the responsible lawyers may see the matter (Chinese wall).
falseidempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
idMatter id. Use it as matter_id elsewhere.
1234case_numberCase number as displayed in Flowyer, in the office numbering scheme.
"2026/0025"subject"Kovács Kft. v. Nagy Bt. - contract dispute"status"active" "archived" case_type_idcase_typeLocalized name of the case type.
responsible_user_idresponsibleDisplay name of the responsible lawyer.
clientsClients (principals) of the matter.
Fields of clients
idnamestarted_onrestrictedTrue when only the responsible lawyers may see the matter (Chinese wall).
created_atupdated_atcreated_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratdescriptionlitigiousarchived_atDay the matter was archived, YYYY-MM-DD. Flowyer records the date only, no time of day.
createdtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
idMatter id. Use it as matter_id elsewhere.
1234case_numberCase number as displayed in Flowyer, in the office numbering scheme.
"2026/0025"subject"Kovács Kft. v. Nagy Bt. - contract dispute"status"active" "archived" case_type_idcase_typeLocalized name of the case type.
responsible_user_idresponsibleDisplay name of the responsible lawyer.
clientsClients (principals) of the matter.
Fields of clients
idnamestarted_onrestrictedTrue when only the responsible lawyers may see the matter (Chinese wall).
created_atupdated_atcreated_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratdescriptionlitigiousarchived_atDay the matter was archived, YYYY-MM-DD. Flowyer records the date only, no time of day.
createdfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/matters' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"subject": "Kovács Kft. v. Nagy Bt. - contract dispute",
"case_type_id": 1234,
"client_ids": [
512
],
"started_on": "2026-10-29"
}'
const response = await fetch("https://www.flowyer.com/api/agent/matters", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"subject": "Kovács Kft. v. Nagy Bt. - contract dispute",
"case_type_id": 1234,
"client_ids": [
512
],
"started_on": "2026-10-29"
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/matters",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"subject": "Kovács Kft. v. Nagy Bt. - contract dispute",
"case_type_id": 1234,
"client_ids": [
512,
],
"started_on": "2026-10-29",
},
timeout=30,
)
print(response.status_code, response.json())
{
"subject": "Kovács Kft. v. Nagy Bt. - contract dispute",
"case_type_id": 1234,
"client_ids": [
512
],
"started_on": "2026-10-29"
}
Response samples
{
"data": {
"id": 1234,
"case_number": "2026/0025",
"subject": "Kovács Kft. v. Nagy Bt. - contract dispute",
"status": "active",
"case_type_id": 1234,
"case_type": "string",
"responsible_user_id": 1234,
"responsible": "string",
"clients": [
{
"id": 1234,
"name": "string"
}
],
"started_on": "2026-10-30",
"restricted": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
},
"description": "string",
"litigious": true,
"archived_at": "2026-10-30"
},
"created": true
}
{
"data": {
"id": 1234,
"case_number": "2026/0025",
"subject": "Kovács Kft. v. Nagy Bt. - contract dispute",
"status": "active",
"case_type_id": 1234,
"case_type": "string",
"responsible_user_id": 1234,
"responsible": "string",
"clients": [
{
"id": 1234,
"name": "string"
}
],
"started_on": "2026-10-30",
"restricted": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
},
"description": "string",
"litigious": true,
"archived_at": "2026-10-30"
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"subject": [
"The subject field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update matter
/api/agent/matters/{id}
Changes a matter's subject, description, start date, litigious flag or responsible lawyer. Case number, Chinese wall, archive status, fees and client links are not editable here. Pass expected_updated_at from your last read; a conflicting change answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
subjectShort title of the matter. Plain text, at most 255 characters as a NEW value; "<" and ">" cannot be introduced either, but a stored subject that is longer or contains them may be sent back unchanged. null keeps the current subject.
descriptionLonger description, plain text, at most 10000 characters as a NEW value; a longer stored description may be sent back unchanged.
started_onStart date, YYYY-MM-DD. null keeps the current value (a start date cannot be cleared).
litigiousWhether the matter is litigious.
responsible_user_idNew responsible lawyer: an active colleague's user id from colleagues.list. They are notified when it is not you. Sending the current one - or null / 0, which a matter without a responsible reads back as - changes nothing.
Responses
200
Updated.
datarequiredFields of data
idMatter id. Use it as matter_id elsewhere.
1234case_numberCase number as displayed in Flowyer, in the office numbering scheme.
"2026/0025"subject"Kovács Kft. v. Nagy Bt. - contract dispute"status"active" "archived" case_type_idcase_typeLocalized name of the case type.
responsible_user_idresponsibleDisplay name of the responsible lawyer.
clientsClients (principals) of the matter.
Fields of clients
idnamestarted_onrestrictedTrue when only the responsible lawyers may see the matter (Chinese wall).
created_atupdated_atcreated_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratdescriptionlitigiousarchived_atDay the matter was archived, YYYY-MM-DD. Flowyer records the date only, no time of day.
updatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/matters/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"subject": "string"
}'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"subject": "string"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/matters/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"subject": "string",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"subject": "string"
}
Response samples
{
"data": {
"id": 1234,
"case_number": "2026/0025",
"subject": "Kovács Kft. v. Nagy Bt. - contract dispute",
"status": "active",
"case_type_id": 1234,
"case_type": "string",
"responsible_user_id": 1234,
"responsible": "string",
"clients": [
{
"id": 1234,
"name": "string"
}
],
"started_on": "2026-10-30",
"restricted": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
},
"description": "string",
"litigious": true,
"archived_at": "2026-10-30"
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
List matter responsibles
/api/agent/matters/{id}/responsibles
Lists the ADDITIONAL responsibles of a matter (the primary responsible is on matters.get): each is a colleague (type "user", user_id set) or a workgroup (type "workgroup", workgroup_id set), all of whom can see the matter. Answers 404 when the matter is not visible.
Path parameters
idrequiredMatter id.
1234Query parameters
limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
type"user" = a single colleague; "workgroup" = every member of a workgroup.
"user" "workgroup" user_idColleague user id for a "user" entry (pass it as user_id to matters.responsibles.add); null for a workgroup.
workgroup_idWorkgroup id for a "workgroup" entry; null for a single colleague. Workgroup responsibles are managed in Flowyer.
nameDisplay name of the colleague or workgroup.
paginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/matters/1234/responsibles?limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/responsibles?limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/matters/1234/responsibles?limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"type": "user",
"user_id": 1234,
"workgroup_id": 1234,
"name": "string"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "matter_not_visible",
"message": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"message_localized": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"matter_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Add matter responsible
/api/agent/matters/{id}/responsibles
Adds a colleague (user_id from colleagues.list) as an additional responsible of a matter. This GIVES that colleague access to the matter, so only add someone who should see it; they are notified. Idempotent: re-adding an existing responsible makes no change.
Path parameters
idrequiredMatter the colleague becomes responsible for (must be visible).
1234Request body application/json
user_idrequiredColleague to add: a user id from colleagues.list. Adding them gives them access to the matter.
42Responses
200
OK.
datarequiredFields of data
user_idThe colleague now responsible for the matter.
nameDisplay name of the colleague.
already_responsibleTrue when the colleague was already a responsible of the matter (nothing changed).
notifiedTrue when the colleague was notified they were added. Never true when the colleague is you, or when they were already responsible.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/matters/1234/responsibles' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"user_id": 42
}'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/responsibles", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"user_id": 42
}),
});
const result = await response.json();
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/matters/1234/responsibles",
headers={
"Authorization": "Bearer flw_...",
},
json={
"user_id": 42,
},
timeout=30,
)
print(response.status_code, response.json())
{
"user_id": 42
}
Response samples
{
"data": {
"user_id": 1234,
"name": "string",
"already_responsible": true,
"notified": true
}
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"user_id": [
"The user id field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Clients
List clients
/api/agent/clients
Lists the office's clients (persons and companies), alphabetically; q searches the name (accents and case ignored), e-mail and tax number. Before creating a client, check the name with clients.resolve (it tolerates typos too). Archived clients are hidden unless include_archived is true.
Query parameters
qSearch in name (accents and case ignored), e-mail and tax number.
"Kovács"is_persontrue = natural persons only, false = companies only.
include_archivedfalselimitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idClient id. Use it as client_id elsewhere.
512name"Kovács Kft."is_personTrue for a natural person, false for a company.
emailphonearchivedrecorded_by_user_idrecorded_bycreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/clients?q=Kov%C3%A1cs&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/clients?q=Kov%C3%A1cs&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/clients?q=Kov%C3%A1cs&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 512,
"name": "Kovács Kft.",
"is_person": true,
"email": "info@example.hu",
"phone": "string",
"archived": true,
"recorded_by_user_id": 1234,
"recorded_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"clients:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"clients:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "clients:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Resolve client by name
/api/agent/clients/resolve
Finds a client by a spoken or misspelled name (accents, casing and small typos tolerated). Returns status exact/similar/none and candidate clients. Only "exact" is safe to use directly; on "similar" or "none" ask the user. A name with many lookalikes is never "exact": ask for a fuller name.
Query parameters
namerequiredClient name to look up, as heard or dictated.
"Józsi ingatlan"limitMaximum candidates to return (default 5).
5Responses
200
OK.
datarequiredFields of data
statusexact = one confident match (safe to use its id). similar = one or more approximate matches, confirm with the user first. none = no match, ask the user.
"exact" "similar" "none" matchesCandidate clients, best match first.
Fields of matches
idClient id. Use it as client_id elsewhere.
512name"Kovács Kft."is_personTrue for a natural person, false for a company.
emailphonearchivedrecorded_by_user_idrecorded_bycreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserattotalHow many clients matched before the list was cut to limit.
truncatedTrue when more clients matched than were returned, or too many lookalikes existed to check them all; ask the user for a fuller name or raise limit rather than choosing from an incomplete list.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/clients/resolve?name=J%C3%B3zsi%20ingatlan' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/clients/resolve?name=J%C3%B3zsi%20ingatlan", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/clients/resolve?name=J%C3%B3zsi%20ingatlan",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"status": "exact",
"matches": [
{
"id": 512,
"name": "Kovács Kft.",
"is_person": true,
"email": "info@example.hu",
"phone": "string",
"archived": true,
"recorded_by_user_id": 1234,
"recorded_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
],
"total": 1,
"truncated": true
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"clients:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"clients:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "clients:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get client
/api/agent/clients/{id}
Returns one client with address and registration details. Identity-document numbers and fee settings are never returned.
Path parameters
idrequiredClient id.
512Responses
200
OK.
datarequiredFields of data
idClient id. Use it as client_id elsewhere.
512name"Kovács Kft."is_personTrue for a natural person, false for a company.
emailphonearchivedrecorded_by_user_idrecorded_bycreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserataddressFields of address
zipcitystreetmailing_addressFields of mailing_address
zipcitystreettax_numberCompanies only; null for a natural person.
company_registration_numberCompanies only; null for a natural person.
representative_nameCompanies only; null for a natural person.
representative_positionCompanies only; null for a natural person.
main_activityMain scope of activity. Companies only; null for a natural person.
nationality_countrynote
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/clients/512' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/clients/512", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/clients/512",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 512,
"name": "Kovács Kft.",
"is_person": true,
"email": "info@example.hu",
"phone": "string",
"archived": true,
"recorded_by_user_id": 1234,
"recorded_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
},
"address": {
"zip": "string",
"city": "string",
"street": "string"
},
"mailing_address": {
"zip": "string",
"city": "string",
"street": "string"
},
"tax_number": "string",
"company_registration_number": "string",
"representative_name": "string",
"representative_position": "string",
"main_activity": "string",
"nationality_country": "string",
"note": "string"
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"client_id": 512
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Create client
/api/agent/clients
Creates a client (person or company), recorded as created by the key's user. Check the name with clients.resolve first (it tolerates missing accents and typos): a 409 only catches an active client of the very same name, unless allow_duplicate_name is true. ID numbers and fees cannot be set.
Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
namerequiredFull name of the person or the company name. Plain text; "<" and ">" are not allowed.
^[^<>]*$"Kovács Kft."is_persontrue for a natural person, false (default) for a company.
falseemail"info@kovacskft.hu"phone"+36 1 234 5678"addressRegistered / home address.
Fields of address
zipcitystreetmailing_addressFields of mailing_address
zipcitystreettax_numberCompany tax number (companies only).
"12345678-2-41"company_registration_numberCompany registration number (companies only).
"01-09-123456"representative_nameCompanies only.
representative_positionCompanies only.
main_activityMain scope of activity (companies only).
nationality_countrynoteallow_duplicate_nameCreate even if an active client with the same name exists.
falseidempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
idClient id. Use it as client_id elsewhere.
512name"Kovács Kft."is_personTrue for a natural person, false for a company.
emailphonearchivedrecorded_by_user_idrecorded_bycreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserataddressFields of address
zipcitystreetmailing_addressFields of mailing_address
zipcitystreettax_numberCompanies only; null for a natural person.
company_registration_numberCompanies only; null for a natural person.
representative_nameCompanies only; null for a natural person.
representative_positionCompanies only; null for a natural person.
main_activityMain scope of activity. Companies only; null for a natural person.
nationality_countrynotecreatedtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
idClient id. Use it as client_id elsewhere.
512name"Kovács Kft."is_personTrue for a natural person, false for a company.
emailphonearchivedrecorded_by_user_idrecorded_bycreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserataddressFields of address
zipcitystreetmailing_addressFields of mailing_address
zipcitystreettax_numberCompanies only; null for a natural person.
company_registration_numberCompanies only; null for a natural person.
representative_nameCompanies only; null for a natural person.
representative_positionCompanies only; null for a natural person.
main_activityMain scope of activity. Companies only; null for a natural person.
nationality_countrynotecreatedfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
An active client with the same name exists (client_exists, or client_exists_generic without the clients:read scope, when the existing client may not be named; pass allow_duplicate_name to override), the Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed (duplicate_in_progress).
client_exists, or client_exists_generic without the clients:read scope, when the existing client may not be named; pass allow_duplicate_name to override), the Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed (duplicate_in_progress).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/clients' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"name": "Kovács Kft.",
"email": "info@kovacskft.hu",
"phone": "+36 1 234 5678",
"tax_number": "12345678-2-41",
"company_registration_number": "01-09-123456"
}'
const response = await fetch("https://www.flowyer.com/api/agent/clients", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"name": "Kovács Kft.",
"email": "info@kovacskft.hu",
"phone": "+36 1 234 5678",
"tax_number": "12345678-2-41",
"company_registration_number": "01-09-123456"
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/clients",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Kovács Kft.",
"email": "info@kovacskft.hu",
"phone": "+36 1 234 5678",
"tax_number": "12345678-2-41",
"company_registration_number": "01-09-123456",
},
timeout=30,
)
print(response.status_code, response.json())
{
"name": "Kovács Kft.",
"email": "info@kovacskft.hu",
"phone": "+36 1 234 5678",
"tax_number": "12345678-2-41",
"company_registration_number": "01-09-123456"
}
Response samples
{
"data": {
"id": 512,
"name": "Kovács Kft.",
"is_person": true,
"email": "info@example.hu",
"phone": "string",
"archived": true,
"recorded_by_user_id": 1234,
"recorded_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
},
"address": {
"zip": "string",
"city": "string",
"street": "string"
},
"mailing_address": {
"zip": "string",
"city": "string",
"street": "string"
},
"tax_number": "string",
"company_registration_number": "string",
"representative_name": "string",
"representative_position": "string",
"main_activity": "string",
"nationality_country": "string",
"note": "string"
},
"created": true
}
{
"data": {
"id": 512,
"name": "Kovács Kft.",
"is_person": true,
"email": "info@example.hu",
"phone": "string",
"archived": true,
"recorded_by_user_id": 1234,
"recorded_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
},
"address": {
"zip": "string",
"city": "string",
"street": "string"
},
"mailing_address": {
"zip": "string",
"city": "string",
"street": "string"
},
"tax_number": "string",
"company_registration_number": "string",
"representative_name": "string",
"representative_position": "string",
"main_activity": "string",
"nationality_country": "string",
"note": "string"
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"name": [
"The name field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update client
/api/agent/clients/{id}
Changes a client's contact fields (name, email, phone, address, note, nationality, and company representative/activity). ID numbers, tax numbers, fees and the person/company type are not editable here. Pass expected_updated_at; a conflicting change answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
nameFull name or company name. Plain text, at most 255 characters as a NEW value; "<" and ">" cannot be introduced either, but a stored name that is longer or contains them may be sent back unchanged. null keeps the current name.
emailphoneaddressRegistered / home address.
Fields of address
zipcitystreetmailing_addressFields of mailing_address
zipcitystreetrepresentative_nameCompanies only.
representative_positionCompanies only.
main_activityMain scope of activity (companies only).
nationality_countrynoteFree-text note, at most 4000 characters as a NEW value; a longer stored note may be sent back unchanged. null clears the note.
Responses
200
Updated.
datarequiredFields of data
idClient id. Use it as client_id elsewhere.
512name"Kovács Kft."is_personTrue for a natural person, false for a company.
emailphonearchivedrecorded_by_user_idrecorded_bycreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserataddressFields of address
zipcitystreetmailing_addressFields of mailing_address
zipcitystreettax_numberCompanies only; null for a natural person.
company_registration_numberCompanies only; null for a natural person.
representative_nameCompanies only; null for a natural person.
representative_positionCompanies only; null for a natural person.
main_activityMain scope of activity. Companies only; null for a natural person.
nationality_countrynoteupdatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/clients/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "string"
}'
const response = await fetch("https://www.flowyer.com/api/agent/clients/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "string"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/clients/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "string",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "string"
}
Response samples
{
"data": {
"id": 512,
"name": "Kovács Kft.",
"is_person": true,
"email": "info@example.hu",
"phone": "string",
"archived": true,
"recorded_by_user_id": 1234,
"recorded_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
},
"address": {
"zip": "string",
"city": "string",
"street": "string"
},
"mailing_address": {
"zip": "string",
"city": "string",
"street": "string"
},
"tax_number": "string",
"company_registration_number": "string",
"representative_name": "string",
"representative_position": "string",
"main_activity": "string",
"nationality_country": "string",
"note": "string"
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Tasks
List tasks
/api/agent/tasks
Lists tasks (to-dos with a deadline) the user may see, soonest deadline first. Open tasks by default; filter by assignee, matter, client or deadline range.
Query parameters
statusopen (default), closed or all.
"open" "closed" "all" "open"assigned_to_user_idOnly tasks assigned to this user (a user id from colleagues.list).
matter_idOnly tasks of this matter.
client_idOnly caseless tasks of this client.
deadline_fromDeadline on or after this date.
"2026-11-01"deadline_toDeadline on or before this date.
"2026-11-30"limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
id9876subject"Submit the statement of defence"descriptiondeadlineDue date, YYYY-MM-DD.
"2026-11-15"priority1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).
2status"open" "closed" closed_atDay the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.
matterThe matter the task belongs to, if any.
Fields of matter
idcase_numberclientThe client: the matter's first client, or the task's own client when it has no matter.
Fields of client
idnameassigned_to_user_idassigned_tocreated_by_user_idcreated_byflag_idcreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/tasks?deadline_from=2026-11-01&deadline_to=2026-11-30&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/tasks?deadline_from=2026-11-01&deadline_to=2026-11-30&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/tasks?deadline_from=2026-11-01&deadline_to=2026-11-30&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 9876,
"subject": "Submit the statement of defence",
"description": "string",
"deadline": "2026-11-15",
"priority": 2,
"status": "open",
"closed_at": "2026-10-30",
"matter": {
"id": 1234,
"case_number": "string"
},
"client": {
"id": 1234,
"name": "string"
},
"assigned_to_user_id": 1234,
"assigned_to": "string",
"created_by_user_id": 1234,
"created_by": "string",
"flag_id": 1234,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"tasks:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"tasks:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "tasks:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get task
/api/agent/tasks/{id}
Returns one task. Answers 404 when the task does not exist or the user may not see it.
Path parameters
idrequiredTask id.
9876Responses
200
OK.
datarequiredFields of data
id9876subject"Submit the statement of defence"descriptiondeadlineDue date, YYYY-MM-DD.
"2026-11-15"priority1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).
2status"open" "closed" closed_atDay the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.
matterThe matter the task belongs to, if any.
Fields of matter
idcase_numberclientThe client: the matter's first client, or the task's own client when it has no matter.
Fields of client
idnameassigned_to_user_idassigned_tocreated_by_user_idcreated_byflag_idcreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserat
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/tasks/9876' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/tasks/9876", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/tasks/9876",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 9876,
"subject": "Submit the statement of defence",
"description": "string",
"deadline": "2026-11-15",
"priority": 2,
"status": "open",
"closed_at": "2026-10-30",
"matter": {
"id": 1234,
"case_number": "string"
},
"client": {
"id": 1234,
"name": "string"
},
"assigned_to_user_id": 1234,
"assigned_to": "string",
"created_by_user_id": 1234,
"created_by": "string",
"flag_id": 1234,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"task_id": 9876
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Create task
/api/agent/tasks
Creates a task (to-do with a deadline), either on a matter (matter_id, visible and not archived) or caseless, optionally for a client (client_id). The assignee defaults to the key's user and is notified when it is someone else.
Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
subjectrequiredWhat has to be done. Plain text.
"Submit the statement of defence"deadlinerequiredDue date, YYYY-MM-DD.
"2026-11-15"matter_idMatter the task belongs to. Omit for a caseless task.
1234client_idClient of a caseless task. Not allowed together with matter_id (a matter's task takes the matter's client).
assigned_to_user_idColleague responsible for the task: a user id from colleagues.list (look a dictated name up there). Default: the user behind the key.
priority1 = low, 2 = medium (default), 3 = high.
2descriptionLonger details, plain text.
flag_idId of an office task flag (label).
idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
id9876subject"Submit the statement of defence"descriptiondeadlineDue date, YYYY-MM-DD.
"2026-11-15"priority1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).
2status"open" "closed" closed_atDay the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.
matterThe matter the task belongs to, if any.
Fields of matter
idcase_numberclientThe client: the matter's first client, or the task's own client when it has no matter.
Fields of client
idnameassigned_to_user_idassigned_tocreated_by_user_idcreated_byflag_idcreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
id9876subject"Submit the statement of defence"descriptiondeadlineDue date, YYYY-MM-DD.
"2026-11-15"priority1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).
2status"open" "closed" closed_atDay the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.
matterThe matter the task belongs to, if any.
Fields of matter
idcase_numberclientThe client: the matter's first client, or the task's own client when it has no matter.
Fields of client
idnameassigned_to_user_idassigned_tocreated_by_user_idcreated_byflag_idcreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/tasks' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"subject": "Submit the statement of defence",
"deadline": "2026-11-15",
"matter_id": 1234
}'
const response = await fetch("https://www.flowyer.com/api/agent/tasks", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"subject": "Submit the statement of defence",
"deadline": "2026-11-15",
"matter_id": 1234
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/tasks",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"subject": "Submit the statement of defence",
"deadline": "2026-11-15",
"matter_id": 1234,
},
timeout=30,
)
print(response.status_code, response.json())
{
"subject": "Submit the statement of defence",
"deadline": "2026-11-15",
"matter_id": 1234
}
Response samples
{
"data": {
"id": 9876,
"subject": "Submit the statement of defence",
"description": "string",
"deadline": "2026-11-15",
"priority": 2,
"status": "open",
"closed_at": "2026-10-30",
"matter": {
"id": 1234,
"case_number": "string"
},
"client": {
"id": 1234,
"name": "string"
},
"assigned_to_user_id": 1234,
"assigned_to": "string",
"created_by_user_id": 1234,
"created_by": "string",
"flag_id": 1234,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": true
}
{
"data": {
"id": 9876,
"subject": "Submit the statement of defence",
"description": "string",
"deadline": "2026-11-15",
"priority": 2,
"status": "open",
"closed_at": "2026-10-30",
"matter": {
"id": 1234,
"case_number": "string"
},
"client": {
"id": 1234,
"name": "string"
},
"assigned_to_user_id": 1234,
"assigned_to": "string",
"created_by_user_id": 1234,
"created_by": "string",
"flag_id": 1234,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"subject": [
"The subject field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update task
/api/agent/tasks/{id}
Changes a task: subject, deadline, description, priority, flag, assignee or the matter it belongs to. Moving it onto a matter re-derives the court link and drops a direct client; client_id is only for a caseless task. Pass expected_updated_at; a conflicting change answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
subjectWhat has to be done. Plain text, at most 1000 characters as a NEW value; a longer stored subject may be sent back unchanged. null keeps the current subject.
deadlineDue date, YYYY-MM-DD. null keeps the current deadline (a deadline cannot be cleared).
matter_idMove the task onto this matter (must be visible and not archived), or null to detach it. Sending the task's current matter changes nothing.
client_idClient of a caseless task. Only allowed when the task has no matter.
assigned_to_user_idNew assignee: an active colleague's user id from colleagues.list. Notified when it is not you. Sending the current one - or null / 0, which a task without an assignee reads back as - changes nothing.
priority1 = low, 2 = medium, 3 = high. Omit it to keep the current priority (also when the task reads back with priority null); null is refused.
descriptionLonger details, plain text, at most 10000 characters as a NEW value; a longer stored description may be sent back unchanged.
flag_idId of an office task flag (label), or null to clear it.
Responses
200
Updated.
datarequiredFields of data
id9876subject"Submit the statement of defence"descriptiondeadlineDue date, YYYY-MM-DD.
"2026-11-15"priority1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).
2status"open" "closed" closed_atDay the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.
matterThe matter the task belongs to, if any.
Fields of matter
idcase_numberclientThe client: the matter's first client, or the task's own client when it has no matter.
Fields of client
idnameassigned_to_user_idassigned_tocreated_by_user_idcreated_byflag_idcreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratupdatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/tasks/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"subject": "string"
}'
const response = await fetch("https://www.flowyer.com/api/agent/tasks/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"subject": "string"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/tasks/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"subject": "string",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"subject": "string"
}
Response samples
{
"data": {
"id": 9876,
"subject": "Submit the statement of defence",
"description": "string",
"deadline": "2026-11-15",
"priority": 2,
"status": "open",
"closed_at": "2026-10-30",
"matter": {
"id": 1234,
"case_number": "string"
},
"client": {
"id": 1234,
"name": "string"
},
"assigned_to_user_id": 1234,
"assigned_to": "string",
"created_by_user_id": 1234,
"created_by": "string",
"flag_id": 1234,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Close task
/api/agent/tasks/{id}/close
Closes a task. If minutes are given it also books a time entry (needs workhours:create and a case: the task's own, or matter_id on a caseless task); billable minutes are clipped to the office hour-cap. A second close answers 409, so a retry never books a second entry.
Path parameters
idrequiredTask id.
9876Request body application/json
minutes_spentTime spent, in minutes. Booking any time needs the workhours:create scope and a case.
minutes_invoiceableBillable minutes; clipped to the office hour-cap.
minutes_normativeNormative minutes; stored raw, never capped.
matter_idAssign this case to a CASELESS task at close (and so make it possible to book time). Not allowed when the task already has a case.
client_idDirect client (Megbízó) for a caseless task closed without a case.
Responses
200
OK.
datarequiredFields of data
id9876subject"Submit the statement of defence"descriptiondeadlineDue date, YYYY-MM-DD.
"2026-11-15"priority1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).
2status"open" "closed" closed_atDay the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.
matterThe matter the task belongs to, if any.
Fields of matter
idcase_numberclientThe client: the matter's first client, or the task's own client when it has no matter.
Fields of client
idnameassigned_to_user_idassigned_tocreated_by_user_idcreated_byflag_idcreated_atupdated_atcreated_via_apiFields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratbooked_workhourThe time entry booked from the close, or null when no time was entered.
Fields of booked_workhour
idminutesminutes_invoiceableminutes_normative
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/tasks/9876/close' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{}'
const response = await fetch("https://www.flowyer.com/api/agent/tasks/9876/close", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});
const result = await response.json();
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/tasks/9876/close",
headers={
"Authorization": "Bearer flw_...",
},
json={},
timeout=30,
)
print(response.status_code, response.json())
{}
Response samples
{
"data": {
"id": 9876,
"subject": "Submit the statement of defence",
"description": "string",
"deadline": "2026-11-15",
"priority": 2,
"status": "open",
"closed_at": "2026-10-30",
"matter": {
"id": 1234,
"case_number": "string"
},
"client": {
"id": 1234,
"name": "string"
},
"assigned_to_user_id": 1234,
"assigned_to": "string",
"created_by_user_id": 1234,
"created_by": "string",
"flag_id": 1234,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
},
"booked_workhour": {
"id": 1234,
"minutes": 0,
"minutes_invoiceable": 0,
"minutes_normative": 0
}
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Workhours
List time entries
/api/agent/workhours
Lists live (not yet invoiced) time entries on matters the user may see, newest first. Filter by matter, responsible colleague or date range. Entries excluded from the summary are listed too, flagged excluded: true; leave them out of billable totals.
Query parameters
matter_idOnly entries on this matter.
1234responsible_user_idOnly entries booked to this colleague (a user id from colleagues.list).
date_fromOnly entries on or after this date (YYYY-MM-DD).
date_toOnly entries on or before this date (YYYY-MM-DD).
limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idTime entry id.
matter_idMatter the entry is booked to.
dateactivityWhat was done (plain text).
minutesMinutes spent.
minutes_invoiceableBillable minutes after the office hour-cap was applied (may be lower than requested). They count towards billing only while excluded is false.
minutes_normativeNormative minutes (informational; never capped).
responsible_user_idColleague the time is booked to.
responsiblerecorded_by_user_idWho recorded the entry.
lockedTrue when the entry is locked (read-only through the API).
excludedTrue when a colleague excluded the entry from the matter summary: it is not billed and its minutes are left out of the billable totals.
created_atupdated_atSend this back as expected_updated_at when updating.
paginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/workhours?matter_id=1234&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/workhours?matter_id=1234&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/workhours?matter_id=1234&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 1234,
"matter_id": 1234,
"date": "2026-10-30",
"activity": "string",
"minutes": 0,
"minutes_invoiceable": 0,
"minutes_normative": 0,
"responsible_user_id": 1234,
"responsible": "string",
"recorded_by_user_id": 1234,
"locked": true,
"excluded": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"workhours:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"workhours:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "workhours:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get time entry
/api/agent/workhours/{id}
Returns one live time entry by id, if its matter is visible to the user.
Path parameters
idrequiredTime entry id.
Responses
200
OK.
datarequiredFields of data
idTime entry id.
matter_idMatter the entry is booked to.
dateactivityWhat was done (plain text).
minutesMinutes spent.
minutes_invoiceableBillable minutes after the office hour-cap was applied (may be lower than requested). They count towards billing only while excluded is false.
minutes_normativeNormative minutes (informational; never capped).
responsible_user_idColleague the time is booked to.
responsiblerecorded_by_user_idWho recorded the entry.
lockedTrue when the entry is locked (read-only through the API).
excludedTrue when a colleague excluded the entry from the matter summary: it is not billed and its minutes are left out of the billable totals.
created_atupdated_atSend this back as expected_updated_at when updating.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/workhours/1234' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/workhours/1234", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/workhours/1234",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 1234,
"matter_id": 1234,
"date": "2026-10-30",
"activity": "string",
"minutes": 0,
"minutes_invoiceable": 0,
"minutes_normative": 0,
"responsible_user_id": 1234,
"responsible": "string",
"recorded_by_user_id": 1234,
"locked": true,
"excluded": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"record_type": "workhour",
"record_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Record time entry
/api/agent/workhours
Records a time entry on a matter: date, activity, minutes spent and billable minutes. Omitted billable minutes mean not billable: send minutes_invoiceable for billable work. They are clipped to the office hour-cap. The responsible colleague defaults to the key's user and is notified otherwise.
Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
matter_idrequiredMatter the time is booked to (must be visible).
1234daterequiredDate of the work, YYYY-MM-DD.
"2026-10-30"subjectWhat was done; the read side returns it as activity and either name is accepted here (one of the two is required). Plain text; "<" and ">" are not allowed.
^[^<>]*$"Drafting the statement of claim"activityThe same field as subject, under the name the read side uses. Send one of the two.
^[^<>]*$minutesMinutes actually spent.
90minutes_invoiceableBillable minutes requested. Omitted or null = not billable (the entry bills nothing; they are not derived from minutes): for billable work send them explicitly, usually equal to minutes. The office hour-cap may reduce the stored value. They count towards billing only while the entry is not excluded from the summary.
60minutes_normativeNormative minutes (informational; never capped).
responsible_user_idColleague the time is booked to: a user id from colleagues.list (look a dictated name up there). Default: the user behind the key.
idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
idTime entry id.
matter_idMatter the entry is booked to.
dateactivityWhat was done (plain text).
minutesMinutes spent.
minutes_invoiceableBillable minutes after the office hour-cap was applied (may be lower than requested). They count towards billing only while excluded is false.
minutes_normativeNormative minutes (informational; never capped).
responsible_user_idColleague the time is booked to.
responsiblerecorded_by_user_idWho recorded the entry.
lockedTrue when the entry is locked (read-only through the API).
excludedTrue when a colleague excluded the entry from the matter summary: it is not billed and its minutes are left out of the billable totals.
created_atupdated_atSend this back as expected_updated_at when updating.
createdtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
idTime entry id.
matter_idMatter the entry is booked to.
dateactivityWhat was done (plain text).
minutesMinutes spent.
minutes_invoiceableBillable minutes after the office hour-cap was applied (may be lower than requested). They count towards billing only while excluded is false.
minutes_normativeNormative minutes (informational; never capped).
responsible_user_idColleague the time is booked to.
responsiblerecorded_by_user_idWho recorded the entry.
lockedTrue when the entry is locked (read-only through the API).
excludedTrue when a colleague excluded the entry from the matter summary: it is not billed and its minutes are left out of the billable totals.
created_atupdated_atSend this back as expected_updated_at when updating.
createdfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/workhours' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"matter_id": 1234,
"date": "2026-10-30",
"subject": "Drafting the statement of claim",
"minutes": 90,
"minutes_invoiceable": 60
}'
const response = await fetch("https://www.flowyer.com/api/agent/workhours", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"matter_id": 1234,
"date": "2026-10-30",
"subject": "Drafting the statement of claim",
"minutes": 90,
"minutes_invoiceable": 60
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/workhours",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"matter_id": 1234,
"date": "2026-10-30",
"subject": "Drafting the statement of claim",
"minutes": 90,
"minutes_invoiceable": 60,
},
timeout=30,
)
print(response.status_code, response.json())
{
"matter_id": 1234,
"date": "2026-10-30",
"subject": "Drafting the statement of claim",
"minutes": 90,
"minutes_invoiceable": 60
}
Response samples
{
"data": {
"id": 1234,
"matter_id": 1234,
"date": "2026-10-30",
"activity": "string",
"minutes": 0,
"minutes_invoiceable": 0,
"minutes_normative": 0,
"responsible_user_id": 1234,
"responsible": "string",
"recorded_by_user_id": 1234,
"locked": true,
"excluded": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"created": true
}
{
"data": {
"id": 1234,
"matter_id": 1234,
"date": "2026-10-30",
"activity": "string",
"minutes": 0,
"minutes_invoiceable": 0,
"minutes_normative": 0,
"responsible_user_id": 1234,
"responsible": "string",
"recorded_by_user_id": 1234,
"locked": true,
"excluded": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"matter_id": [
"The matter id field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update time entry
/api/agent/workhours/{id}
Changes a live time entry: date, activity (subject or activity), minutes, billable minutes (a changed number is clipped to the hour-cap, an echoed one kept) and the responsible colleague. A locked or invoiced entry cannot be changed (409). Pass expected_updated_at; a conflict answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
dateDate of the work, YYYY-MM-DD. null keeps the current date.
subjectWhat was done, the value the read side returns as activity (either name is accepted; null under one of them is "not given", only two different texts are refused). Plain text, at most 1000 characters as a NEW value; "<" and ">" cannot be introduced either, but a stored text that is longer or contains them may be sent back unchanged. null keeps the current text.
activityThe same field as subject, under the name the read side uses. Send one of the two (null under the other one is fine).
minutesMinutes actually spent; null clears them.
minutes_invoiceableBillable minutes requested. A number different from the stored one is clipped to what is left of the hour-cap; re-sending the value the read returned keeps it as recorded (it is not clipped again). null clears them. They count towards billing only while the entry is not excluded from the summary (excluded is false).
minutes_normativeNormative minutes (never capped); null clears them.
responsible_user_idNew responsible colleague: an active colleague's user id from colleagues.list; notified when it is not you. Sending the current one - or null / 0, which an entry without a responsible reads back as - changes nothing.
Responses
200
Updated.
datarequiredFields of data
idTime entry id.
matter_idMatter the entry is booked to.
dateactivityWhat was done (plain text).
minutesMinutes spent.
minutes_invoiceableBillable minutes after the office hour-cap was applied (may be lower than requested). They count towards billing only while excluded is false.
minutes_normativeNormative minutes (informational; never capped).
responsible_user_idColleague the time is booked to.
responsiblerecorded_by_user_idWho recorded the entry.
lockedTrue when the entry is locked (read-only through the API).
excludedTrue when a colleague excluded the entry from the matter summary: it is not billed and its minutes are left out of the billable totals.
created_atupdated_atSend this back as expected_updated_at when updating.
updatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at), or it has been invoiced / locked and can no longer be modified through the API (record_locked; make the change in Flowyer).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at), or it has been invoiced / locked and can no longer be modified through the API (record_locked; make the change in Flowyer).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/workhours/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-10-30"
}'
const response = await fetch("https://www.flowyer.com/api/agent/workhours/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-10-30"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/workhours/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-10-30",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-10-30"
}
Response samples
{
"data": {
"id": 1234,
"matter_id": 1234,
"date": "2026-10-30",
"activity": "string",
"minutes": 0,
"minutes_invoiceable": 0,
"minutes_normative": 0,
"responsible_user_id": 1234,
"responsible": "string",
"recorded_by_user_id": 1234,
"locked": true,
"excluded": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Costs
List costs
/api/agent/costs
Lists the live costs of matters the user may see, newest first (an invoiced cost is no longer here). Filter by matter, responsible colleague or date range. Amounts are net. Rows flagged excluded: true are out of the summary and rows flagged locked: true are managed by Flowyer.
Query parameters
matter_idOnly costs on this matter.
1234responsible_user_idOnly costs recorded for this colleague (a user id from colleagues.list).
date_fromOnly costs on or after this date (YYYY-MM-DD).
date_toOnly costs on or before this date (YYYY-MM-DD).
limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idCost id.
matter_idMatter the cost belongs to.
dateactivityDescription of the cost (plain text).
costNet cost amount (no VAT).
cost_invoiceableNet amount to bill on to the client. It counts towards billing only while excluded is false.
currency_idCurrency id (Flowyer currency list).
currencyCurrency ISO code, e.g. HUF, EUR.
responsible_user_idresponsibleexcludedTrue when a colleague excluded the cost from the matter summary: it is not billed and is left out of the billable totals.
lockedTrue when the cost is managed by Flowyer - mirrored from an incoming invoice, or a disbursement surcharge - and cannot be changed through the API (costs.update answers 409 record_locked, details.reason says which).
approved_for_billingTrue when a colleague approved the cost for billing in the matter summary. It stays editable through the API (as in the web), and the approval is NOT re-requested afterwards: say so before changing the amount of an approved cost.
created_atupdated_atSend this back as expected_updated_at when updating.
paginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/costs?matter_id=1234&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/costs?matter_id=1234&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/costs?matter_id=1234&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 1234,
"matter_id": 1234,
"date": "2026-10-30",
"activity": "string",
"cost": 0,
"cost_invoiceable": 0,
"currency_id": 1234,
"currency": "string",
"responsible_user_id": 1234,
"responsible": "string",
"excluded": true,
"locked": true,
"approved_for_billing": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"costs:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"costs:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "costs:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get cost
/api/agent/costs/{id}
Returns one live cost by id, if its matter is visible to the user. Amounts are net.
Path parameters
idrequiredCost id.
Responses
200
OK.
datarequiredFields of data
idCost id.
matter_idMatter the cost belongs to.
dateactivityDescription of the cost (plain text).
costNet cost amount (no VAT).
cost_invoiceableNet amount to bill on to the client. It counts towards billing only while excluded is false.
currency_idCurrency id (Flowyer currency list).
currencyCurrency ISO code, e.g. HUF, EUR.
responsible_user_idresponsibleexcludedTrue when a colleague excluded the cost from the matter summary: it is not billed and is left out of the billable totals.
lockedTrue when the cost is managed by Flowyer - mirrored from an incoming invoice, or a disbursement surcharge - and cannot be changed through the API (costs.update answers 409 record_locked, details.reason says which).
approved_for_billingTrue when a colleague approved the cost for billing in the matter summary. It stays editable through the API (as in the web), and the approval is NOT re-requested afterwards: say so before changing the amount of an approved cost.
created_atupdated_atSend this back as expected_updated_at when updating.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/costs/1234' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/costs/1234", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/costs/1234",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 1234,
"matter_id": 1234,
"date": "2026-10-30",
"activity": "string",
"cost": 0,
"cost_invoiceable": 0,
"currency_id": 1234,
"currency": "string",
"responsible_user_id": 1234,
"responsible": "string",
"excluded": true,
"locked": true,
"approved_for_billing": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"record_type": "cost",
"record_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Record cost
/api/agent/costs
Records a cost (disbursement) on a matter: date, text (subject, read back as activity), net amount, net amount billed to the client, and currency (default: the office currency). Amounts are net (no VAT). The responsible colleague defaults to the key's user and is notified otherwise.
Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
matter_idrequiredMatter the cost belongs to (must be visible).
1234daterequiredDate of the cost, YYYY-MM-DD.
"2026-10-30"subjectDescription of the cost; the read side returns it as activity and either name is accepted here (one of the two is required). Plain text; "<" and ">" are not allowed.
^[^<>]*$"Court filing fee"activityThe same field as subject, under the name the read side uses. Send one of the two.
^[^<>]*$costrequiredNet cost amount (no VAT), a whole number in the currency's main unit (cents are not stored).
15000cost_invoiceableNet amount to bill on to the client, a whole number (omit if not billed). It counts towards billing only while the cost is not excluded from the summary.
15000currency_idCurrency id from the Flowyer currency list (not an ISO code). Omit it to use the office currency (GET /me office.currency_id / office.currency); an unknown id answers 422 with the accepted ids and codes in details.allowed.
responsible_user_idResponsible colleague: a user id from colleagues.list (look a dictated name up there). Default: the user behind the key.
idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
idCost id.
matter_idMatter the cost belongs to.
dateactivityDescription of the cost (plain text).
costNet cost amount (no VAT).
cost_invoiceableNet amount to bill on to the client. It counts towards billing only while excluded is false.
currency_idCurrency id (Flowyer currency list).
currencyCurrency ISO code, e.g. HUF, EUR.
responsible_user_idresponsibleexcludedTrue when a colleague excluded the cost from the matter summary: it is not billed and is left out of the billable totals.
lockedTrue when the cost is managed by Flowyer - mirrored from an incoming invoice, or a disbursement surcharge - and cannot be changed through the API (costs.update answers 409 record_locked, details.reason says which).
approved_for_billingTrue when a colleague approved the cost for billing in the matter summary. It stays editable through the API (as in the web), and the approval is NOT re-requested afterwards: say so before changing the amount of an approved cost.
created_atupdated_atSend this back as expected_updated_at when updating.
createdtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
idCost id.
matter_idMatter the cost belongs to.
dateactivityDescription of the cost (plain text).
costNet cost amount (no VAT).
cost_invoiceableNet amount to bill on to the client. It counts towards billing only while excluded is false.
currency_idCurrency id (Flowyer currency list).
currencyCurrency ISO code, e.g. HUF, EUR.
responsible_user_idresponsibleexcludedTrue when a colleague excluded the cost from the matter summary: it is not billed and is left out of the billable totals.
lockedTrue when the cost is managed by Flowyer - mirrored from an incoming invoice, or a disbursement surcharge - and cannot be changed through the API (costs.update answers 409 record_locked, details.reason says which).
approved_for_billingTrue when a colleague approved the cost for billing in the matter summary. It stays editable through the API (as in the web), and the approval is NOT re-requested afterwards: say so before changing the amount of an approved cost.
created_atupdated_atSend this back as expected_updated_at when updating.
createdfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/costs' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"matter_id": 1234,
"date": "2026-10-30",
"subject": "Court filing fee",
"cost": 15000,
"cost_invoiceable": 15000
}'
const response = await fetch("https://www.flowyer.com/api/agent/costs", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"matter_id": 1234,
"date": "2026-10-30",
"subject": "Court filing fee",
"cost": 15000,
"cost_invoiceable": 15000
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/costs",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"matter_id": 1234,
"date": "2026-10-30",
"subject": "Court filing fee",
"cost": 15000,
"cost_invoiceable": 15000,
},
timeout=30,
)
print(response.status_code, response.json())
{
"matter_id": 1234,
"date": "2026-10-30",
"subject": "Court filing fee",
"cost": 15000,
"cost_invoiceable": 15000
}
Response samples
{
"data": {
"id": 1234,
"matter_id": 1234,
"date": "2026-10-30",
"activity": "string",
"cost": 0,
"cost_invoiceable": 0,
"currency_id": 1234,
"currency": "string",
"responsible_user_id": 1234,
"responsible": "string",
"excluded": true,
"locked": true,
"approved_for_billing": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"created": true
}
{
"data": {
"id": 1234,
"matter_id": 1234,
"date": "2026-10-30",
"activity": "string",
"cost": 0,
"cost_invoiceable": 0,
"currency_id": 1234,
"currency": "string",
"responsible_user_id": 1234,
"responsible": "string",
"excluded": true,
"locked": true,
"approved_for_billing": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"matter_id": [
"The matter id field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update cost
/api/agent/costs/{id}
Changes a live cost: text (subject, read back as activity), date, net amounts, currency and the responsible colleague. A cost flagged locked: true cannot be changed (409 record_locked, details.reason). Pass expected_updated_at; a conflicting change answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
dateDate of the cost, YYYY-MM-DD. null keeps the current date.
subjectDescription of the cost, the value the read side returns as activity (either name is accepted; null under one of them is "not given", only two different texts are refused). Plain text, at most 1000 characters as a NEW value; "<" and ">" cannot be introduced either, but a stored text that is longer or contains them may be sent back unchanged. null keeps the current text.
activityThe same field as subject, under the name the read side uses. Send one of the two (null under the other one is fine).
costNet cost amount (no VAT), a whole number in the currency's main unit (cents are not stored). null clears the amount.
cost_invoiceableNet amount to bill on to the client, a whole number; null clears it. It counts towards billing only while the cost is not excluded from the summary (excluded is false).
currency_idCurrency id from the Flowyer currency list (not an ISO code; the office currency is GET /me office.currency_id). An unknown id answers 422 with the accepted ids and codes in details.allowed. null - or the 0 an unset legacy cost reads back as - keeps the current currency.
responsible_user_idNew responsible colleague: an active colleague's user id from colleagues.list. Sending the current one - or null / 0, which a cost without a responsible reads back as - changes nothing.
Responses
200
Updated.
datarequiredFields of data
idCost id.
matter_idMatter the cost belongs to.
dateactivityDescription of the cost (plain text).
costNet cost amount (no VAT).
cost_invoiceableNet amount to bill on to the client. It counts towards billing only while excluded is false.
currency_idCurrency id (Flowyer currency list).
currencyCurrency ISO code, e.g. HUF, EUR.
responsible_user_idresponsibleexcludedTrue when a colleague excluded the cost from the matter summary: it is not billed and is left out of the billable totals.
lockedTrue when the cost is managed by Flowyer - mirrored from an incoming invoice, or a disbursement surcharge - and cannot be changed through the API (costs.update answers 409 record_locked, details.reason says which).
approved_for_billingTrue when a colleague approved the cost for billing in the matter summary. It stays editable through the API (as in the web), and the approval is NOT re-requested afterwards: say so before changing the amount of an approved cost.
created_atupdated_atSend this back as expected_updated_at when updating.
updatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at), or it has been invoiced / locked and can no longer be modified through the API (record_locked; make the change in Flowyer).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at), or it has been invoiced / locked and can no longer be modified through the API (record_locked; make the change in Flowyer).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/costs/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-10-30"
}'
const response = await fetch("https://www.flowyer.com/api/agent/costs/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-10-30"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/costs/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-10-30",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-10-30"
}
Response samples
{
"data": {
"id": 1234,
"matter_id": 1234,
"date": "2026-10-30",
"activity": "string",
"cost": 0,
"cost_invoiceable": 0,
"currency_id": 1234,
"currency": "string",
"responsible_user_id": 1234,
"responsible": "string",
"excluded": true,
"locked": true,
"approved_for_billing": true,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Invoices
List invoices
/api/agent/invoices
Lists invoices (issued and received), newest issue date first. direction=outgoing is the office's issued-invoice list and leaves proformas out (direction=proforma lists those). Read-only: invoices cannot be created or changed through the API. Invoices of matters the user may not see are hidden.
Query parameters
directionThe office's own three lists: outgoing = invoices issued by the office, proformas excluded (a legacy row with no document type counts as an invoice, as in the office dashboard); proforma = payment requests (díjbekérő) issued by the office; incoming = received.
"incoming" "outgoing" "proforma" paid_status"unpaid" "paid" "partially_paid" "cancelled" client_idmatter_idissued_fromIssue date on or after.
"2026-01-01"issued_toIssue date on or before.
"2026-12-31"limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
id301invoice_number"FLW-2026-0042"directionoutgoing = issued by the office, incoming = received by the office.
"incoming" "outgoing" document_type"proforma" is a request for payment, not an issued invoice: it is not revenue and must not be counted with the issued ones. invoices.list direction=outgoing leaves proformas out, direction=proforma lists them. Null on a legacy row, which counts as an invoice.
"invoice" "proforma" "receipt" source"manual" "szamlazz_outgoing" "szamlazz_imported" paid_status"unpaid" "paid" "partially_paid" "cancelled" issued_datedue_datepaid_datecompletion_datenet_amountgross_amountcurrency"HUF"payment_method_idpayment_methodLocalized name of the payment method (e.g. bank transfer); English when the office language has no translation for it, null when the method has no name at all. Identify the method by payment_method_id.
supplierIssuer of an incoming invoice.
clientFields of client
idnamematter_idcreated_atupdated_atpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/invoices?issued_from=2026-01-01&issued_to=2026-12-31&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/invoices?issued_from=2026-01-01&issued_to=2026-12-31&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/invoices?issued_from=2026-01-01&issued_to=2026-12-31&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 301,
"invoice_number": "FLW-2026-0042",
"direction": "incoming",
"document_type": "invoice",
"source": "manual",
"paid_status": "unpaid",
"issued_date": "2026-10-30",
"due_date": "2026-10-30",
"paid_date": "2026-10-30",
"completion_date": "2026-10-30",
"net_amount": 0,
"gross_amount": 0,
"currency": "HUF",
"payment_method_id": 1234,
"payment_method": "string",
"supplier": "string",
"client": {
"id": 1234,
"name": "string"
},
"matter_id": 1234,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"invoices:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"invoices:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "invoices:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get invoice
/api/agent/invoices/{id}
Returns one invoice. Read-only. Answers 404 when it does not exist or belongs to a matter the user may not see.
Path parameters
idrequiredInvoice id.
301Responses
200
OK.
datarequiredFields of data
id301invoice_number"FLW-2026-0042"directionoutgoing = issued by the office, incoming = received by the office.
"incoming" "outgoing" document_type"proforma" is a request for payment, not an issued invoice: it is not revenue and must not be counted with the issued ones. invoices.list direction=outgoing leaves proformas out, direction=proforma lists them. Null on a legacy row, which counts as an invoice.
"invoice" "proforma" "receipt" source"manual" "szamlazz_outgoing" "szamlazz_imported" paid_status"unpaid" "paid" "partially_paid" "cancelled" issued_datedue_datepaid_datecompletion_datenet_amountgross_amountcurrency"HUF"payment_method_idpayment_methodLocalized name of the payment method (e.g. bank transfer); English when the office language has no translation for it, null when the method has no name at all. Identify the method by payment_method_id.
supplierIssuer of an incoming invoice.
clientFields of client
idnamematter_idcreated_atupdated_atnotesreceived_datesent_dateproforma_invoice_id
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/invoices/301' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/invoices/301", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/invoices/301",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 301,
"invoice_number": "FLW-2026-0042",
"direction": "incoming",
"document_type": "invoice",
"source": "manual",
"paid_status": "unpaid",
"issued_date": "2026-10-30",
"due_date": "2026-10-30",
"paid_date": "2026-10-30",
"completion_date": "2026-10-30",
"net_amount": 0,
"gross_amount": 0,
"currency": "HUF",
"payment_method_id": 1234,
"payment_method": "string",
"supplier": "string",
"client": {
"id": 1234,
"name": "string"
},
"matter_id": 1234,
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"notes": "string",
"received_date": "2026-10-30",
"sent_date": "2026-10-30",
"proforma_invoice_id": 1234
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"invoice_id": 301
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Documents
List matter documents
/api/agent/matters/{id}/documents
Lists the current versions of the documents filed under a matter: names, sizes, folders and who uploaded them. Metadata only; use documents.download (scope documents:download) for a signed link to the contents. Named documents come first, alphabetically, then imported ones that carry no name.
Path parameters
idrequiredMatter id.
1234Query parameters
qFilter by file name.
"contract"limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idnameFile name as shown in Flowyer: the name stored with the document, or its storage name when it was imported from an older system without one. documents.list is ordered by the stored name: documents that have one come first, alphabetically, then the imported ones without a stored name, oldest first (the order they were filed in Flowyer), which are therefore not sorted by the name shown here. The order is the same on every page, so limit and offset page through the list without repeats or gaps.
"Contract_v2.docx"sizeBytes.
versionCurrent version number of the file.
folderThe folder the document is filed in, or null at the matter root. documents.upload.complete answers null when the folder chosen at documents.upload.init was deleted while the bytes were uploading: the document is then filed at the matter root, which is where Flowyer shows it. If the root already holds a document of that name, the upload is filed under a suffixed name ("Contract_2.pdf") instead of becoming a new version of that unrelated document; the 201 echoes the stored name.
Fields of folder
idnamepathFolder path inside the matter, "/" separated.
folder_deletedTrue when the document is filed in a folder that no longer exists, which Flowyer's own file list does not show (folder is null for it, as it is for a document at the matter root). documents.upload.complete answers true only if the folder was deleted while the bytes were uploading AND moving the stored file to the matter root failed: the document is then kept in the deleted folder, where its contents are still reachable. False for everything else, a normal root filing included.
created_by_user_idcreated_bycreated_atupdated_atpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/matters/1234/documents?q=contract&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/documents?q=contract&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/matters/1234/documents?q=contract&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 1234,
"name": "Contract_v2.docx",
"size": 0,
"version": 0,
"folder": {
"id": 1234,
"name": "string",
"path": "string"
},
"folder_deleted": true,
"created_by_user_id": 1234,
"created_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "matter_not_visible",
"message": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"message_localized": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"matter_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
List matter folders
/api/agent/matters/{id}/folders
Lists every folder of a matter, empty ones included: id, name, parent_id and the "/"-separated path, ordered by path. Pass a folder id as folder_id to documents.upload.init. Answers 404 when the matter is not visible to the user.
Path parameters
idrequiredMatter id.
1234Query parameters
qOnly folders whose path contains this text (case and accents ignored), e.g. a folder name.
"Beadványok"limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idFolder id: pass it as folder_id to documents.upload.init.
nameFolder name as shown in Flowyer.
parent_idId of the parent folder; null for a folder at the matter root.
pathFolder path inside the matter, "/" separated, e.g. "/Iratok/Beadványok" (the same path documents.list shows for a file's folder).
updated_atPass this to documents.folders.rename as expected_updated_at.
paginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/matters/1234/folders?q=Beadv%C3%A1nyok&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/folders?q=Beadv%C3%A1nyok&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/matters/1234/folders?q=Beadv%C3%A1nyok&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 1234,
"name": "string",
"parent_id": 1234,
"path": "string",
"updated_at": "2026-10-30T09:15:00+01:00"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "matter_not_visible",
"message": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"message_localized": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"matter_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Create matter folder
/api/agent/matters/{id}/folders
Creates a folder in a matter, at the root or under parent_id (a folder of the SAME matter). The name is plain text ("<" and ">" are not allowed). File a document into it by passing the returned id as folder_id to documents.upload.init. Answers 404 when the matter is not visible.
Path parameters
idrequiredMatter the folder belongs to (must be visible).
1234Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
namerequiredFolder name, at most 255 characters. Plain text; "<" and ">" are not allowed.
^[^<>]*$"Beadványok"parent_idParent folder id (from documents.folders) inside the SAME matter; omit for a folder at the matter root.
idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
idFolder id: pass it as folder_id to documents.upload.init, or as parent_id to documents.folders.create.
nameFolder name as shown in Flowyer.
parent_idId of the parent folder; null for a folder at the matter root.
pathFolder path inside the matter, "/" separated, e.g. "/Iratok/Beadványok".
updated_atPass this to documents.folders.rename as expected_updated_at.
createdtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
idFolder id: pass it as folder_id to documents.upload.init, or as parent_id to documents.folders.create.
nameFolder name as shown in Flowyer.
parent_idId of the parent folder; null for a folder at the matter root.
pathFolder path inside the matter, "/" separated, e.g. "/Iratok/Beadványok".
updated_atPass this to documents.folders.rename as expected_updated_at.
createdfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/matters/1234/folders' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"name": "Beadványok"
}'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/folders", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"name": "Beadványok"
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/matters/1234/folders",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Beadványok",
},
timeout=30,
)
print(response.status_code, response.json())
{
"name": "Beadványok"
}
Response samples
{
"data": {
"id": 1234,
"name": "string",
"parent_id": 1234,
"path": "string",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"created": true
}
{
"data": {
"id": 1234,
"name": "string",
"parent_id": 1234,
"path": "string",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"name": [
"The name field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Start document upload
/api/agent/matters/{id}/documents/uploads
Starts a document upload: returns a signed upload_url and an upload_token. PUT the bytes there, then call documents.upload.complete. The PUT is a plain HTTPS request outside this API and outside MCP tools: an assistant without its own HTTP client should ask the user to upload the file in Flowyer.
Path parameters
idrequiredMatter the document belongs to (must be visible).
Request body application/json
filenamerequiredFile name with extension, e.g. "Contract.pdf". Plain text; "<" and ">" are not allowed. Executable and web file types are refused (422 blocked_file_type).
^[^<>]*$"Contract.pdf"content_typerequiredMIME type sent on the PUT, e.g. "application/pdf". It is part of the signature and must match exactly.
"application/pdf"sizerequiredFile size in bytes, at most 104857600 (the upload limit); a larger size answers 413 file_too_large.
248000folder_idFolder inside the matter: a folder id from documents.folders (it lists empty folders too). Omit for the matter root.
Responses
200
OK.
datarequiredFields of data
upload_urlSigned URL. PUT the file bytes here with the given content type AND the if_generation_match header below: a plain HTTPS request made by your own HTTP client (no API or MCP tool sends it).
upload_tokenPass this to documents.upload.complete after the PUT succeeds.
content_typeSend exactly this Content-Type on the PUT.
if_generation_matchSend this value as the "x-goog-if-generation-match" header on the PUT (always "0"). It makes the upload single-use: the PUT is accepted only while the object does not yet exist, so the signed URL cannot overwrite an already-filed document.
object_keyInternal storage name (for reference).
expires_inSeconds until the upload URL and token expire.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
413
The request body is too large (request_too_large), or the file exceeds the upload limit (file_too_large; details.max gives the limit in bytes).
request_too_large), or the file exceeds the upload limit (file_too_large; details.max gives the limit in bytes).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/matters/1234/documents/uploads' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"filename": "Contract.pdf",
"content_type": "application/pdf",
"size": 248000
}'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/documents/uploads", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"filename": "Contract.pdf",
"content_type": "application/pdf",
"size": 248000
}),
});
const result = await response.json();
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/matters/1234/documents/uploads",
headers={
"Authorization": "Bearer flw_...",
},
json={
"filename": "Contract.pdf",
"content_type": "application/pdf",
"size": 248000,
},
timeout=30,
)
print(response.status_code, response.json())
{
"filename": "Contract.pdf",
"content_type": "application/pdf",
"size": 248000
}
Response samples
{
"data": {
"upload_url": "string",
"upload_token": "string",
"content_type": "string",
"if_generation_match": "string",
"object_key": "string",
"expires_in": 0
}
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"filename": [
"The filename field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Finish document upload
/api/agent/matters/{id}/documents
Finishes a document upload: after the bytes were PUT to the upload_url, records the file on the matter as a new version. Pass the upload_token from documents.upload.init. The file metadata is taken from the token, not from this request. A folder deleted meanwhile files the document at the root.
Path parameters
idrequiredMatter the document belongs to (same as in init).
Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
upload_tokenrequiredThe token returned by documents.upload.init.
idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
idnameFile name as shown in Flowyer: the name stored with the document, or its storage name when it was imported from an older system without one. documents.list is ordered by the stored name: documents that have one come first, alphabetically, then the imported ones without a stored name, oldest first (the order they were filed in Flowyer), which are therefore not sorted by the name shown here. The order is the same on every page, so limit and offset page through the list without repeats or gaps.
"Contract_v2.docx"sizeBytes.
versionCurrent version number of the file.
folderThe folder the document is filed in, or null at the matter root. documents.upload.complete answers null when the folder chosen at documents.upload.init was deleted while the bytes were uploading: the document is then filed at the matter root, which is where Flowyer shows it. If the root already holds a document of that name, the upload is filed under a suffixed name ("Contract_2.pdf") instead of becoming a new version of that unrelated document; the 201 echoes the stored name.
Fields of folder
idnamepathFolder path inside the matter, "/" separated.
folder_deletedTrue when the document is filed in a folder that no longer exists, which Flowyer's own file list does not show (folder is null for it, as it is for a document at the matter root). documents.upload.complete answers true only if the folder was deleted while the bytes were uploading AND moving the stored file to the matter root failed: the document is then kept in the deleted folder, where its contents are still reachable. False for everything else, a normal root filing included.
created_by_user_idcreated_bycreated_atupdated_atcreatedtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created. Also the answer when this upload_token was already completed.
datarequiredFields of data
idnameFile name as shown in Flowyer: the name stored with the document, or its storage name when it was imported from an older system without one. documents.list is ordered by the stored name: documents that have one come first, alphabetically, then the imported ones without a stored name, oldest first (the order they were filed in Flowyer), which are therefore not sorted by the name shown here. The order is the same on every page, so limit and offset page through the list without repeats or gaps.
"Contract_v2.docx"sizeBytes.
versionCurrent version number of the file.
folderThe folder the document is filed in, or null at the matter root. documents.upload.complete answers null when the folder chosen at documents.upload.init was deleted while the bytes were uploading: the document is then filed at the matter root, which is where Flowyer shows it. If the root already holds a document of that name, the upload is filed under a suffixed name ("Contract_2.pdf") instead of becoming a new version of that unrelated document; the 201 echoes the stored name.
Fields of folder
idnamepathFolder path inside the matter, "/" separated.
folder_deletedTrue when the document is filed in a folder that no longer exists, which Flowyer's own file list does not show (folder is null for it, as it is for a document at the matter root). documents.upload.complete answers true only if the folder was deleted while the bytes were uploading AND moving the stored file to the matter root failed: the document is then kept in the deleted folder, where its contents are still reachable. False for everything else, a normal root filing included.
created_by_user_idcreated_bycreated_atupdated_atcreatedfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. upload_not_finished: the file has not reached storage yet; finish the PUT to upload_url, then call complete again. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
upload_not_finished: the file has not reached storage yet; finish the PUT to upload_url, then call complete again. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large), or the file exceeds the upload limit (file_too_large; details.max gives the limit in bytes).
request_too_large), or the file exceeds the upload limit (file_too_large; details.max gives the limit in bytes).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/matters/1234/documents' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"upload_token": "string"
}'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/documents", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"upload_token": "string"
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/matters/1234/documents",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"upload_token": "string",
},
timeout=30,
)
print(response.status_code, response.json())
{
"upload_token": "string"
}
Response samples
{
"data": {
"id": 1234,
"name": "Contract_v2.docx",
"size": 0,
"version": 0,
"folder": {
"id": 1234,
"name": "string",
"path": "string"
},
"folder_deleted": true,
"created_by_user_id": 1234,
"created_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"created": true
}
{
"data": {
"id": 1234,
"name": "Contract_v2.docx",
"size": 0,
"version": 0,
"folder": {
"id": 1234,
"name": "string",
"path": "string"
},
"folder_deleted": true,
"created_by_user_id": 1234,
"created_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"upload_token": [
"The upload token field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Download document
/api/agent/documents/{id}/download
Returns a short-lived signed URL to download one document by id, if its matter is visible. GET the url to fetch the bytes; it expires in a few minutes.
Path parameters
idrequiredDocument id (from documents.list).
Responses
200
OK.
datarequiredFields of data
download_urlSigned URL. GET it to download the file bytes.
filenameFile name for the download.
sizeBytes.
expires_inSeconds until the URL expires.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/documents/1234/download' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/documents/1234/download", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/documents/1234/download",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"download_url": "string",
"filename": "string",
"size": 0,
"expires_in": 0
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"record_type": "document",
"record_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Rename document
/api/agent/documents/{id}
Renames a document (its display name only; the file content is unchanged). The new name must be unique in the same folder of the matter. Pass expected_updated_at from your last read; a conflicting change answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
namerequiredNew file name, at most 255 characters INCLUDING the extension. The current extension is kept if you omit one, and it counts towards the 255 (so an extension-less name for "Beadvany.pdf" may be at most 251 characters). Plain text; "<" and ">" are not allowed, so a document whose stored name contains one can only be renamed in Flowyer.
^[^<>]*$"Contract_signed.pdf"Responses
200
Updated.
datarequiredFields of data
idnameFile name as shown in Flowyer: the name stored with the document, or its storage name when it was imported from an older system without one. documents.list is ordered by the stored name: documents that have one come first, alphabetically, then the imported ones without a stored name, oldest first (the order they were filed in Flowyer), which are therefore not sorted by the name shown here. The order is the same on every page, so limit and offset page through the list without repeats or gaps.
"Contract_v2.docx"sizeBytes.
versionCurrent version number of the file.
folderThe folder the document is filed in, or null at the matter root. documents.upload.complete answers null when the folder chosen at documents.upload.init was deleted while the bytes were uploading: the document is then filed at the matter root, which is where Flowyer shows it. If the root already holds a document of that name, the upload is filed under a suffixed name ("Contract_2.pdf") instead of becoming a new version of that unrelated document; the 201 echoes the stored name.
Fields of folder
idnamepathFolder path inside the matter, "/" separated.
folder_deletedTrue when the document is filed in a folder that no longer exists, which Flowyer's own file list does not show (folder is null for it, as it is for a document at the matter root). documents.upload.complete answers true only if the folder was deleted while the bytes were uploading AND moving the stored file to the matter root failed: the document is then kept in the deleted folder, where its contents are still reachable. False for everything else, a normal root filing included.
created_by_user_idcreated_bycreated_atupdated_atupdatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at), or another document in the same folder already has this name (duplicate_name; choose another name, do not retry the same one).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at), or another document in the same folder already has this name (duplicate_name; choose another name, do not retry the same one).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/documents/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "Contract_signed.pdf"
}'
const response = await fetch("https://www.flowyer.com/api/agent/documents/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "Contract_signed.pdf"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/documents/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "Contract_signed.pdf",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "Contract_signed.pdf"
}
Response samples
{
"data": {
"id": 1234,
"name": "Contract_v2.docx",
"size": 0,
"version": 0,
"folder": {
"id": 1234,
"name": "string",
"path": "string"
},
"folder_deleted": true,
"created_by_user_id": 1234,
"created_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Rename matter folder
/api/agent/folders/{id}
Renames a folder (its name only; its files and subfolders keep their place). The name is plain text ("<" and ">" are not allowed). Pass expected_updated_at from your last read (documents.folders lists it); a conflicting change answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
namerequiredNew folder name, at most 255 characters. Plain text; "<" and ">" are not allowed.
^[^<>]*$"Beadványok"Responses
200
Updated.
datarequiredFields of data
idFolder id: pass it as folder_id to documents.upload.init, or as parent_id to documents.folders.create.
nameFolder name as shown in Flowyer.
parent_idId of the parent folder; null for a folder at the matter root.
pathFolder path inside the matter, "/" separated, e.g. "/Iratok/Beadványok".
updated_atPass this to documents.folders.rename as expected_updated_at.
updatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/folders/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "Beadványok"
}'
const response = await fetch("https://www.flowyer.com/api/agent/folders/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "Beadványok"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/folders/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "Beadványok",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "Beadványok"
}
Response samples
{
"data": {
"id": 1234,
"name": "string",
"parent_id": 1234,
"path": "string",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Knowledge base
List knowledge base articles
/api/agent/knowledge/articles
Lists knowledge-base articles the user may see (the author's own, global articles, and articles shared with the user's role). All statuses by default; filter by status, or pass q to full-text search titles, excerpts, content and tags.
Query parameters
qFull-text search term (titles, excerpts, content and tag names). Boolean operators are stripped.
"felmondási idő"statusOnly articles with this status. Omit to list every status.
"draft" "published" "archived" limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
id9876slugServer-generated URL slug.
"felmondasi-ido"title"Felmondási idő a munka törvénykönyvében"excerptShort summary (rich HTML, as stored). Use knowledge.articles.get for the full body.
status"draft" "published" "archived" is_featuredWhether the article is pinned to the KB home page (not settable through the API).
category_idcategory_nameauthor_idUser id of the author; 0 for a global (default) article.
author_nameeditor_idUser id of the last editor, or null if never edited.
editor_nameviews_countdownloads_countshared_rolesRole ids the article is shared with (empty = only the author/global).
tagsTag names on the article.
published_atWhen the article was first published, or null.
created_atupdated_atpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/knowledge/articles?q=felmond%C3%A1si%20id%C5%91&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/knowledge/articles?q=felmond%C3%A1si%20id%C5%91&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/knowledge/articles?q=felmond%C3%A1si%20id%C5%91&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 9876,
"slug": "felmondasi-ido",
"title": "Felmondási idő a munka törvénykönyvében",
"excerpt": "string",
"status": "draft",
"is_featured": true,
"category_id": 1234,
"category_name": "string",
"author_id": 1234,
"author_name": "string",
"editor_id": 1234,
"editor_name": "string",
"views_count": 0,
"downloads_count": 0,
"shared_roles": [
0
],
"tags": [
"string"
],
"published_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"knowledge:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"knowledge:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "knowledge:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get knowledge base article
/api/agent/knowledge/articles/{id}
Returns one knowledge-base article, including its full content, tags and shared roles. Answers 404 when the article does not exist or the user may not see it.
Path parameters
idrequiredArticle id.
9876Responses
200
OK.
datarequiredFields of data
id9876slugServer-generated URL slug.
"felmondasi-ido"title"Felmondási idő a munka törvénykönyvében"excerptShort summary (rich HTML, as stored). Use knowledge.articles.get for the full body.
status"draft" "published" "archived" is_featuredWhether the article is pinned to the KB home page (not settable through the API).
category_idcategory_nameauthor_idUser id of the author; 0 for a global (default) article.
author_nameeditor_idUser id of the last editor, or null if never edited.
editor_nameviews_countdownloads_countshared_rolesRole ids the article is shared with (empty = only the author/global).
tagsTag names on the article.
published_atWhen the article was first published, or null.
created_atupdated_atcontentThe full article body as stored (rich HTML).
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/knowledge/articles/9876' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/knowledge/articles/9876", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/knowledge/articles/9876",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 9876,
"slug": "felmondasi-ido",
"title": "Felmondási idő a munka törvénykönyvében",
"excerpt": "string",
"status": "draft",
"is_featured": true,
"category_id": 1234,
"category_name": "string",
"author_id": 1234,
"author_name": "string",
"editor_id": 1234,
"editor_name": "string",
"views_count": 0,
"downloads_count": 0,
"shared_roles": [
0
],
"tags": [
"string"
],
"published_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"content": "string"
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"article_id": 9876
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Create knowledge base article
/api/agent/knowledge/articles
Creates a knowledge-base article authored by the key's user. The slug is generated from the title. The category must exist and be active. Share it with roles via shared_roles (otherwise only the author sees it). content and excerpt are HTML and are sanitised server-side.
Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
titlerequiredArticle title. Plain text.
"Felmondási idő a munka törvénykönyvében"excerptShort summary (HTML allowed, sanitised server-side), at most 200 characters.
contentrequiredThe article body. Rich HTML, sanitised server-side with the knowledge-base profile.
category_idrequiredId of an existing, active knowledge-base category.
12statusrequireddraft, published or archived. published stamps published_at.
"draft" "published" "archived" tagsTag names; new tags are created as needed.
shared_rolesRole ids to share the article with. Empty means only the author can see it.
idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
id9876slugServer-generated URL slug.
"felmondasi-ido"title"Felmondási idő a munka törvénykönyvében"excerptShort summary (rich HTML, as stored). Use knowledge.articles.get for the full body.
status"draft" "published" "archived" is_featuredWhether the article is pinned to the KB home page (not settable through the API).
category_idcategory_nameauthor_idUser id of the author; 0 for a global (default) article.
author_nameeditor_idUser id of the last editor, or null if never edited.
editor_nameviews_countdownloads_countshared_rolesRole ids the article is shared with (empty = only the author/global).
tagsTag names on the article.
published_atWhen the article was first published, or null.
created_atupdated_atcontentThe full article body as stored (rich HTML).
createdtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
id9876slugServer-generated URL slug.
"felmondasi-ido"title"Felmondási idő a munka törvénykönyvében"excerptShort summary (rich HTML, as stored). Use knowledge.articles.get for the full body.
status"draft" "published" "archived" is_featuredWhether the article is pinned to the KB home page (not settable through the API).
category_idcategory_nameauthor_idUser id of the author; 0 for a global (default) article.
author_nameeditor_idUser id of the last editor, or null if never edited.
editor_nameviews_countdownloads_countshared_rolesRole ids the article is shared with (empty = only the author/global).
tagsTag names on the article.
published_atWhen the article was first published, or null.
created_atupdated_atcontentThe full article body as stored (rich HTML).
createdfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/knowledge/articles' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"title": "Felmondási idő a munka törvénykönyvében",
"content": "string",
"category_id": 12,
"status": "draft"
}'
const response = await fetch("https://www.flowyer.com/api/agent/knowledge/articles", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"title": "Felmondási idő a munka törvénykönyvében",
"content": "string",
"category_id": 12,
"status": "draft"
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/knowledge/articles",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"title": "Felmondási idő a munka törvénykönyvében",
"content": "string",
"category_id": 12,
"status": "draft",
},
timeout=30,
)
print(response.status_code, response.json())
{
"title": "Felmondási idő a munka törvénykönyvében",
"content": "string",
"category_id": 12,
"status": "draft"
}
Response samples
{
"data": {
"id": 9876,
"slug": "felmondasi-ido",
"title": "Felmondási idő a munka törvénykönyvében",
"excerpt": "string",
"status": "draft",
"is_featured": true,
"category_id": 1234,
"category_name": "string",
"author_id": 1234,
"author_name": "string",
"editor_id": 1234,
"editor_name": "string",
"views_count": 0,
"downloads_count": 0,
"shared_roles": [
0
],
"tags": [
"string"
],
"published_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"content": "string"
},
"created": true
}
{
"data": {
"id": 9876,
"slug": "felmondasi-ido",
"title": "Felmondási idő a munka törvénykönyvében",
"excerpt": "string",
"status": "draft",
"is_featured": true,
"category_id": 1234,
"category_name": "string",
"author_id": 1234,
"author_name": "string",
"editor_id": 1234,
"editor_name": "string",
"views_count": 0,
"downloads_count": 0,
"shared_roles": [
0
],
"tags": [
"string"
],
"published_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"content": "string"
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"title": [
"The title field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update knowledge base article
/api/agent/knowledge/articles/{id}
Changes a knowledge-base article: title (the slug is re-derived), excerpt, content, category, status, tags or shared roles. Only the author (or anyone, for a global article) may edit. Pass expected_updated_at; a conflicting change answers 409. content and excerpt are sanitised server-side.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
titleArticle title (plain text). null keeps the current title; a new title re-derives the slug.
excerptShort summary (HTML allowed, sanitised), at most 200 characters as a NEW value. Empty/null clears it; omit to keep it.
contentThe article body (rich HTML, sanitised). null keeps the current content; it cannot be cleared.
category_idMove the article to this existing, active category. null keeps the current one.
statusdraft, published or archived. The first switch to published stamps published_at; it is never cleared. null keeps the current status.
"draft" "published" "archived" tagsReplaces the whole tag set (new tags are created). An empty array clears all tags; omit to keep them.
shared_rolesReplaces the whole shared-role set. An empty array removes all sharing (only the author sees it); omit to keep it.
Responses
200
Updated.
datarequiredFields of data
id9876slugServer-generated URL slug.
"felmondasi-ido"title"Felmondási idő a munka törvénykönyvében"excerptShort summary (rich HTML, as stored). Use knowledge.articles.get for the full body.
status"draft" "published" "archived" is_featuredWhether the article is pinned to the KB home page (not settable through the API).
category_idcategory_nameauthor_idUser id of the author; 0 for a global (default) article.
author_nameeditor_idUser id of the last editor, or null if never edited.
editor_nameviews_countdownloads_countshared_rolesRole ids the article is shared with (empty = only the author/global).
tagsTag names on the article.
published_atWhen the article was first published, or null.
created_atupdated_atcontentThe full article body as stored (rich HTML).
updatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/knowledge/articles/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "string"
}'
const response = await fetch("https://www.flowyer.com/api/agent/knowledge/articles/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "string"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/knowledge/articles/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "string",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "string"
}
Response samples
{
"data": {
"id": 9876,
"slug": "felmondasi-ido",
"title": "Felmondási idő a munka törvénykönyvében",
"excerpt": "string",
"status": "draft",
"is_featured": true,
"category_id": 1234,
"category_name": "string",
"author_id": 1234,
"author_name": "string",
"editor_id": 1234,
"editor_name": "string",
"views_count": 0,
"downloads_count": 0,
"shared_roles": [
0
],
"tags": [
"string"
],
"published_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"content": "string"
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
List knowledge base categories
/api/agent/knowledge/categories
Lists the knowledge base categories the user may see: built-in default categories, the user's own, and those shared with the user's role. Ordered as in Flowyer (sort position, then name).
Query parameters
limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
id42slugURL slug, generated from the name.
"munkajog"name"Munkajog"descriptionPlain-text description, or null.
iconIcon identifier shown next to the category, or null.
order_columnSort position among sibling categories.
0is_activeparent_idParent category id, or null for a root category.
author_idUser id of the creator; 0 for a built-in default category visible to everyone.
author_nameDisplay name of the creator, or null for a default category.
articles_countNumber of published articles in this category that the caller may see.
shared_rolesRole ids this category is shared with. Empty means only the author sees it (a default category with author_id 0 is visible to everyone).
created_atupdated_atpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/knowledge/categories?limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/knowledge/categories?limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/knowledge/categories?limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 42,
"slug": "munkajog",
"name": "Munkajog",
"description": "string",
"icon": "string",
"order_column": 0,
"is_active": true,
"parent_id": 1234,
"author_id": 1234,
"author_name": "string",
"articles_count": 0,
"shared_roles": [
1
],
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"knowledge:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"knowledge:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "knowledge:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get knowledge base category
/api/agent/knowledge/categories/{id}
Returns one knowledge base category. Answers 404 when the category does not exist or is not visible to the user who owns this API key.
Path parameters
idrequiredCategory id.
42Responses
200
OK.
datarequiredFields of data
id42slugURL slug, generated from the name.
"munkajog"name"Munkajog"descriptionPlain-text description, or null.
iconIcon identifier shown next to the category, or null.
order_columnSort position among sibling categories.
0is_activeparent_idParent category id, or null for a root category.
author_idUser id of the creator; 0 for a built-in default category visible to everyone.
author_nameDisplay name of the creator, or null for a default category.
articles_countNumber of published articles in this category that the caller may see.
shared_rolesRole ids this category is shared with. Empty means only the author sees it (a default category with author_id 0 is visible to everyone).
created_atupdated_at
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/knowledge/categories/42' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/knowledge/categories/42", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/knowledge/categories/42",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 42,
"slug": "munkajog",
"name": "Munkajog",
"description": "string",
"icon": "string",
"order_column": 0,
"is_active": true,
"parent_id": 1234,
"author_id": 1234,
"author_name": "string",
"articles_count": 0,
"shared_roles": [
1
],
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"category_id": 42
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Create knowledge base category
/api/agent/knowledge/categories
Creates a knowledge base category. The URL slug is generated from the name. Share it with roles via shared_roles, or omit them so only you (the author) can see it. A parent_id, when given, must be an existing active category.
Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
namerequiredCategory name. Plain text; "<" and ">" are not allowed.
^[^<>]*$"Munkajog"descriptionOptional description, plain text.
parent_idParent category id (must exist and be active). Omit for a root category.
iconOptional icon identifier. Plain text; "<" and ">" are not allowed.
^[^<>]*$is_activeWhether the category is active. Default true.
trueorder_columnSort position among sibling categories. Default 0.
0shared_rolesRole ids to share this category with. Omit so only you (the author) can see it.
idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
id42slugURL slug, generated from the name.
"munkajog"name"Munkajog"descriptionPlain-text description, or null.
iconIcon identifier shown next to the category, or null.
order_columnSort position among sibling categories.
0is_activeparent_idParent category id, or null for a root category.
author_idUser id of the creator; 0 for a built-in default category visible to everyone.
author_nameDisplay name of the creator, or null for a default category.
articles_countNumber of published articles in this category that the caller may see.
shared_rolesRole ids this category is shared with. Empty means only the author sees it (a default category with author_id 0 is visible to everyone).
created_atupdated_atcreatedtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
id42slugURL slug, generated from the name.
"munkajog"name"Munkajog"descriptionPlain-text description, or null.
iconIcon identifier shown next to the category, or null.
order_columnSort position among sibling categories.
0is_activeparent_idParent category id, or null for a root category.
author_idUser id of the creator; 0 for a built-in default category visible to everyone.
author_nameDisplay name of the creator, or null for a default category.
articles_countNumber of published articles in this category that the caller may see.
shared_rolesRole ids this category is shared with. Empty means only the author sees it (a default category with author_id 0 is visible to everyone).
created_atupdated_atcreatedfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/knowledge/categories' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"name": "Munkajog",
"order_column": 0
}'
const response = await fetch("https://www.flowyer.com/api/agent/knowledge/categories", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"name": "Munkajog",
"order_column": 0
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/knowledge/categories",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"name": "Munkajog",
"order_column": 0,
},
timeout=30,
)
print(response.status_code, response.json())
{
"name": "Munkajog",
"order_column": 0
}
Response samples
{
"data": {
"id": 42,
"slug": "munkajog",
"name": "Munkajog",
"description": "string",
"icon": "string",
"order_column": 0,
"is_active": true,
"parent_id": 1234,
"author_id": 1234,
"author_name": "string",
"articles_count": 0,
"shared_roles": [
1
],
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"created": true
}
{
"data": {
"id": 42,
"slug": "munkajog",
"name": "Munkajog",
"description": "string",
"icon": "string",
"order_column": 0,
"is_active": true,
"parent_id": 1234,
"author_id": 1234,
"author_name": "string",
"articles_count": 0,
"shared_roles": [
1
],
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"name": [
"The name field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update knowledge base category
/api/agent/knowledge/categories/{id}
Changes a knowledge base category: name (the slug follows), description, icon, parent, active flag, sort position or the roles it is shared with. Only the author may change it (a built-in default category is editable by anyone). Pass expected_updated_at; a conflicting change answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
nameCategory name. At most 255 characters as a NEW value; "<" and ">" cannot be introduced, but a stored name that is longer or contains them may be sent back unchanged. null keeps the current name.
descriptionPlain-text description. null clears it.
parent_idMove the category under this parent (must exist, be active and not create a cycle), or null to make it a root category.
iconIcon identifier. At most 100 characters as a NEW value; "<" and ">" cannot be introduced. null clears it.
is_activeActive flag. Omit or null to keep the current value.
order_columnSort position among sibling categories. Omit or null to keep the current value.
shared_rolesReplace the set of role ids this category is shared with. An empty array removes all sharing (only the author sees it). Omit to leave sharing unchanged.
Responses
200
Updated.
datarequiredFields of data
id42slugURL slug, generated from the name.
"munkajog"name"Munkajog"descriptionPlain-text description, or null.
iconIcon identifier shown next to the category, or null.
order_columnSort position among sibling categories.
0is_activeparent_idParent category id, or null for a root category.
author_idUser id of the creator; 0 for a built-in default category visible to everyone.
author_nameDisplay name of the creator, or null for a default category.
articles_countNumber of published articles in this category that the caller may see.
shared_rolesRole ids this category is shared with. Empty means only the author sees it (a default category with author_id 0 is visible to everyone).
created_atupdated_atupdatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/knowledge/categories/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "string"
}'
const response = await fetch("https://www.flowyer.com/api/agent/knowledge/categories/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "string"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/knowledge/categories/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "string",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"name": "string"
}
Response samples
{
"data": {
"id": 42,
"slug": "munkajog",
"name": "Munkajog",
"description": "string",
"icon": "string",
"order_column": 0,
"is_active": true,
"parent_id": 1234,
"author_id": 1234,
"author_name": "string",
"articles_count": 0,
"shared_roles": [
1
],
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Emails
List matter emails
/api/agent/matters/{id}/emails
Lists the emails saved onto a matter: sender name and address, subject, size and the colleague who saved each one, newest sent first. Header and metadata only, never the body or the raw HTML. Use emails.get for a single message's plain-text body.
Path parameters
idrequiredMatter id.
1234Query parameters
qFilter by subject, sender name or sender address (accent- and case-insensitive). Not the body.
"invoice"limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idEmail id (use with emails.get).
1234from_nameDisplay name of the outside party. The store keeps no direction or recipient columns, so this is always the external party, never a colleague, and the API cannot say whether the mail was incoming or outgoing.
from_addressSMTP address of the outside party.
subjectsent_atWhen the message was sent. The stored value's timezone is inconsistent by ingest path (the IMAP worker stores UTC, the Outlook add-in stores an office-zone wall time), so it is best-effort and the list order by it can be off by the office UTC offset across sources. created_at is the reliable timestamp.
sizeBytes of the stored .eml/.msg file.
extension"msg" (Outlook add-in) or "eml" (IMAP / default); null on some legacy rows.
saved_by_user_idUser id of the colleague who saved the mail into Flowyer.
saved_byDisplay name of that colleague.
created_atWhen the mail was saved into Flowyer (office-zone wall time). The reliable timestamp for stable pagination.
paginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/matters/1234/emails?q=invoice&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/emails?q=invoice&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/matters/1234/emails?q=invoice&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 1234,
"from_name": "string",
"from_address": "string",
"subject": "string",
"sent_at": "2026-10-30T09:15:00+01:00",
"size": 0,
"extension": "string",
"saved_by_user_id": 1234,
"saved_by": "string",
"created_at": "2026-10-30T09:15:00+01:00"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "matter_not_visible",
"message": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"message_localized": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"matter_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get email
/api/agent/emails/{id}
Returns one saved email of a matter with its plain-text body (sender, subject, size and who saved it). The raw HTML body is never returned. Answers 404 when the email does not exist or its matter is not visible to the user who owns this API key.
Path parameters
idrequiredEmail id.
1234Responses
200
OK.
datarequiredFields of data
id1234matterThe matter the mail is filed under, or null for a caseless mail (saved outside any case).
Fields of matter
idcase_numberfrom_nameDisplay name of the outside party (never a colleague; the store has no direction column).
from_addressSMTP address of the outside party.
subjectbodyThe message body as plain text. The raw HTML body is deliberately never returned (untrusted third-party HTML). Best-effort plain text: if the original HTML carried <style>/<script>, their inner text may survive into it.
sent_atWhen the message was sent. Timezone is inconsistent by ingest path (IMAP stores UTC, the Outlook add-in an office-zone wall time); created_at is the reliable timestamp.
sizeBytes of the stored .eml/.msg file.
extension"msg" or "eml"; null on some legacy rows.
saved_by_user_idUser id of the colleague who saved the mail into Flowyer.
saved_byDisplay name of that colleague.
created_atWhen the mail was saved into Flowyer (office-zone wall time).
updated_at
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/emails/1234' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/emails/1234", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/emails/1234",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 1234,
"matter": {
"id": 1234,
"case_number": "string"
},
"from_name": "string",
"from_address": "string",
"subject": "string",
"body": "string",
"sent_at": "2026-10-30T09:15:00+01:00",
"size": 0,
"extension": "string",
"saved_by_user_id": 1234,
"saved_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00"
}
}
{
"error": "email_not_visible",
"message": "The email :id does not exist, or is on a matter that is not visible to the user who owns this API key.",
"message_localized": "The email :id does not exist, or is on a matter that is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"email_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Litigation
List matter litigation history
/api/agent/matters/{id}/litigation
Lists the litigation-history entries (pertörténet) of a matter, newest event first: a dated note with a title, an HTML description and a location. Answers 404 when the matter is not visible to the user.
Path parameters
idrequiredMatter id.
1234Query parameters
limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
id4521titleEntry title. Plain text.
"Első tárgyalás elhalasztva"descriptionEntry details as rich HTML, sanitised server-side.
locationWhere it happened (plain text), e.g. a court room; nullable.
event_dateDay of the entry, YYYY-MM-DD. Flowyer records the date only, no time of day.
user_idAuthor (the user who created the entry).
created_byDisplay name of the author.
last_editor_idUser who last edited the entry.
last_editorDisplay name of the last editor.
created_atupdated_atPass this to litigation.update as expected_updated_at.
created_via_apiPresent when an external agent created the entry through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/matters/1234/litigation?limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/litigation?limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/matters/1234/litigation?limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 4521,
"title": "Első tárgyalás elhalasztva",
"description": "string",
"location": "string",
"event_date": "2026-10-30",
"user_id": 1234,
"created_by": "string",
"last_editor_id": 1234,
"last_editor": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "matter_not_visible",
"message": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"message_localized": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"matter_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Create litigation history entry
/api/agent/matters/{id}/litigation
Adds a litigation-history entry (pertörténet) to a matter: a title (plain text, "<"/">" not allowed), an optional HTML description (sanitised server-side), an optional location and an event date (defaults to today). Answers 404 when the matter is not visible.
Path parameters
idrequiredMatter the entry belongs to (must be visible).
1234Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
titlerequiredEntry title, at most 2000 characters. Plain text; "<" and ">" are not allowed.
^[^<>]*$"Első tárgyalás elhalasztva"descriptionEntry details. Rich HTML, sanitised server-side with the knowledge-base profile.
locationWhere it happened (plain text, at most 255 characters); "<" and ">" are not allowed.
^[^<>]*$"Fővárosi Törvényszék, 215. tárgyaló"event_dateDay of the entry, YYYY-MM-DD. Defaults to today (office time zone).
"2026-11-15"idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
id4521titleEntry title. Plain text.
"Első tárgyalás elhalasztva"descriptionEntry details as rich HTML, sanitised server-side.
locationWhere it happened (plain text), e.g. a court room; nullable.
event_dateDay of the entry, YYYY-MM-DD. Flowyer records the date only, no time of day.
user_idAuthor (the user who created the entry).
created_byDisplay name of the author.
last_editor_idUser who last edited the entry.
last_editorDisplay name of the last editor.
created_atupdated_atPass this to litigation.update as expected_updated_at.
created_via_apiPresent when an external agent created the entry through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
id4521titleEntry title. Plain text.
"Első tárgyalás elhalasztva"descriptionEntry details as rich HTML, sanitised server-side.
locationWhere it happened (plain text), e.g. a court room; nullable.
event_dateDay of the entry, YYYY-MM-DD. Flowyer records the date only, no time of day.
user_idAuthor (the user who created the entry).
created_byDisplay name of the author.
last_editor_idUser who last edited the entry.
last_editorDisplay name of the last editor.
created_atupdated_atPass this to litigation.update as expected_updated_at.
created_via_apiPresent when an external agent created the entry through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/matters/1234/litigation' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"title": "Első tárgyalás elhalasztva",
"location": "Fővárosi Törvényszék, 215. tárgyaló",
"event_date": "2026-11-15"
}'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/litigation", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"title": "Első tárgyalás elhalasztva",
"location": "Fővárosi Törvényszék, 215. tárgyaló",
"event_date": "2026-11-15"
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/matters/1234/litigation",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"title": "Első tárgyalás elhalasztva",
"location": "Fővárosi Törvényszék, 215. tárgyaló",
"event_date": "2026-11-15",
},
timeout=30,
)
print(response.status_code, response.json())
{
"title": "Első tárgyalás elhalasztva",
"location": "Fővárosi Törvényszék, 215. tárgyaló",
"event_date": "2026-11-15"
}
Response samples
{
"data": {
"id": 4521,
"title": "Első tárgyalás elhalasztva",
"description": "string",
"location": "string",
"event_date": "2026-10-30",
"user_id": 1234,
"created_by": "string",
"last_editor_id": 1234,
"last_editor": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": true
}
{
"data": {
"id": 4521,
"title": "Első tárgyalás elhalasztva",
"description": "string",
"location": "string",
"event_date": "2026-10-30",
"user_id": 1234,
"created_by": "string",
"last_editor_id": 1234,
"last_editor": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"title": [
"The title field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update litigation history entry
/api/agent/litigation/{id}
Changes a litigation-history entry (pertörténet): title, description, location or event date. Only the fields you send are written; the description is sanitised server-side. Pass expected_updated_at from your last read; a conflicting change answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
titleNew title, at most 2000 characters (plain text; "<"/">" not allowed). Omit to keep the current one.
^[^<>]*$"Ítélethirdetés elhalasztva"descriptionNew details (rich HTML, sanitised). Omit to keep the current one; null/empty clears it.
locationNew location (plain text, at most 255 characters; "<"/">" not allowed). Omit to keep it; null/empty clears it.
^[^<>]*$event_dateNew event day, YYYY-MM-DD. Omit to keep it; null clears it.
Responses
200
Updated.
datarequiredFields of data
id4521titleEntry title. Plain text.
"Első tárgyalás elhalasztva"descriptionEntry details as rich HTML, sanitised server-side.
locationWhere it happened (plain text), e.g. a court room; nullable.
event_dateDay of the entry, YYYY-MM-DD. Flowyer records the date only, no time of day.
user_idAuthor (the user who created the entry).
created_byDisplay name of the author.
last_editor_idUser who last edited the entry.
last_editorDisplay name of the last editor.
created_atupdated_atPass this to litigation.update as expected_updated_at.
created_via_apiPresent when an external agent created the entry through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratupdatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/litigation/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "Ítélethirdetés elhalasztva"
}'
const response = await fetch("https://www.flowyer.com/api/agent/litigation/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "Ítélethirdetés elhalasztva"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/litigation/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "Ítélethirdetés elhalasztva",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "Ítélethirdetés elhalasztva"
}
Response samples
{
"data": {
"id": 4521,
"title": "Első tárgyalás elhalasztva",
"description": "string",
"location": "string",
"event_date": "2026-10-30",
"user_id": 1234,
"created_by": "string",
"last_editor_id": 1234,
"last_editor": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Mail register
List mail register
/api/agent/mail-register
Lists the mail-register (Postakönyv) entries filed against the matters the user may see, newest first. Filter by matter, direction, free text (subject/sender/addressee/tracking number) and date range. Mail of archived matters is hidden unless include_archived is true.
Query parameters
matter_idOnly entries of this matter (must be visible to the user).
1234directionOnly incoming (received) or outgoing (sent) entries.
"incoming" "outgoing" qFree-text search over subject, sender, addressee, recipient and tracking number (case ignored).
"Kúria"date_fromOnly entries whose date is on or after this day (YYYY-MM-DD).
"2026-01-01"date_toOnly entries whose date is on or before this day (YYYY-MM-DD).
"2026-12-31"updated_sinceOnly entries changed at or after this time (ISO 8601).
"2026-10-01T00:00:00+02:00"include_archivedInclude entries of archived matters.
falselimitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idMail-register entry id (use with mail_register.get / .update).
4321matterThe matter the entry is filed against.
Fields of matter
idcase_numberdirectionincoming = received, outgoing = sent.
"incoming" "outgoing" datePostal/recorded date, YYYY-MM-DD (no time of day).
subjectsenderFeladó: the sender (free text).
toCímzett: the addressee (free text).
recipientFor an incoming mail: the display name of the colleague who received it. For an outgoing mail: the free-text addressee/received-by.
recipient_user_idSet only for an incoming mail whose recipient is a colleague (a user id); null for an outgoing mail.
electronicTrue when the item was recorded as electronic (type_of_mail).
delivery_method_idId of the delivery method (mail_delivery_methods).
delivery_methodLocalized delivery-method label (Posta, Cégkapu, Perkapu, ...), or the electronic/non-electronic fallback for a legacy row.
tracking_noAzonosító: tracking number.
folder_pathrecorded_by_user_idThe colleague who recorded the entry.
recorded_bycreated_atupdated_atPass this to mail_register.update as expected_updated_at.
created_via_apiPresent when an external agent created the entry through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/mail-register?matter_id=1234&q=K%C3%BAria&date_from=2026-01-01' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/mail-register?matter_id=1234&q=K%C3%BAria&date_from=2026-01-01", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/mail-register?matter_id=1234&q=K%C3%BAria&date_from=2026-01-01",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 4321,
"matter": {
"id": 1234,
"case_number": "string"
},
"direction": "incoming",
"date": "2026-10-30",
"subject": "string",
"sender": "string",
"to": "string",
"recipient": "string",
"recipient_user_id": 1234,
"electronic": true,
"delivery_method_id": 1234,
"delivery_method": "string",
"tracking_no": "string",
"folder_path": "string",
"recorded_by_user_id": 1234,
"recorded_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"mail_register:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"mail_register:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "mail_register:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get mail register entry
/api/agent/mail-register/{id}
Returns one mail-register (Postakönyv) entry by id, with its updated_at (the token mail_register.update takes). Answers 404 when the entry does not exist or its matter is not visible to the user.
Path parameters
idrequiredMail-register entry id.
4321Responses
200
OK.
datarequiredFields of data
idMail-register entry id (use with mail_register.get / .update).
4321matterThe matter the entry is filed against.
Fields of matter
idcase_numberdirectionincoming = received, outgoing = sent.
"incoming" "outgoing" datePostal/recorded date, YYYY-MM-DD (no time of day).
subjectsenderFeladó: the sender (free text).
toCímzett: the addressee (free text).
recipientFor an incoming mail: the display name of the colleague who received it. For an outgoing mail: the free-text addressee/received-by.
recipient_user_idSet only for an incoming mail whose recipient is a colleague (a user id); null for an outgoing mail.
electronicTrue when the item was recorded as electronic (type_of_mail).
delivery_method_idId of the delivery method (mail_delivery_methods).
delivery_methodLocalized delivery-method label (Posta, Cégkapu, Perkapu, ...), or the electronic/non-electronic fallback for a legacy row.
tracking_noAzonosító: tracking number.
folder_pathrecorded_by_user_idThe colleague who recorded the entry.
recorded_bycreated_atupdated_atPass this to mail_register.update as expected_updated_at.
created_via_apiPresent when an external agent created the entry through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserat
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/mail-register/4321' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/mail-register/4321", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/mail-register/4321",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 4321,
"matter": {
"id": 1234,
"case_number": "string"
},
"direction": "incoming",
"date": "2026-10-30",
"subject": "string",
"sender": "string",
"to": "string",
"recipient": "string",
"recipient_user_id": 1234,
"electronic": true,
"delivery_method_id": 1234,
"delivery_method": "string",
"tracking_no": "string",
"folder_path": "string",
"recorded_by_user_id": 1234,
"recorded_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"record_type": "mail",
"record_id": 4321
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Create mail register entry
/api/agent/mail-register
Files a new mail-register (Postakönyv) entry on a matter. Use recipient_user_id for an incoming mail received by a colleague, or recipient (free text) for an outgoing one. delivery_method_id must be a known method. Answers 404 when the matter is not visible.
Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
matter_idrequiredMatter the entry belongs to (must be visible).
1234directionrequiredincoming = received, outgoing = sent.
"incoming" "outgoing" daterequiredPostal/recorded date, YYYY-MM-DD (no time of day).
"2026-11-03"subjectrequiredSubject of the correspondence. Plain text; "<" and ">" are not allowed.
^[^<>]*$"Fellebbezés a Kúriához"senderFeladó: the sender (free text). Plain text; "<" and ">" are not allowed.
^[^<>]*$toCímzett: the addressee (free text). Plain text; "<" and ">" are not allowed.
^[^<>]*$recipientOutgoing mail: the free-text addressee/received-by. Not allowed together with recipient_user_id. Plain text; "<" and ">" are not allowed.
^[^<>]*$recipient_user_idIncoming mail: the colleague who received it (a user id from colleagues.list), stored as the numeric recipient. Not allowed together with recipient.
electronicWhether the item is electronic (type_of_mail); independent of the delivery method. Default false.
falsedelivery_method_idDelivery method id (mail_delivery_methods): e.g. 1 Posta, 4 Cégkapu, 7 Perkapu. 422 when unknown.
tracking_noAzonosító: tracking number. Plain text; "<" and ">" are not allowed.
^[^<>]*$folder_path^[^<>]*$idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
idMail-register entry id (use with mail_register.get / .update).
4321matterThe matter the entry is filed against.
Fields of matter
idcase_numberdirectionincoming = received, outgoing = sent.
"incoming" "outgoing" datePostal/recorded date, YYYY-MM-DD (no time of day).
subjectsenderFeladó: the sender (free text).
toCímzett: the addressee (free text).
recipientFor an incoming mail: the display name of the colleague who received it. For an outgoing mail: the free-text addressee/received-by.
recipient_user_idSet only for an incoming mail whose recipient is a colleague (a user id); null for an outgoing mail.
electronicTrue when the item was recorded as electronic (type_of_mail).
delivery_method_idId of the delivery method (mail_delivery_methods).
delivery_methodLocalized delivery-method label (Posta, Cégkapu, Perkapu, ...), or the electronic/non-electronic fallback for a legacy row.
tracking_noAzonosító: tracking number.
folder_pathrecorded_by_user_idThe colleague who recorded the entry.
recorded_bycreated_atupdated_atPass this to mail_register.update as expected_updated_at.
created_via_apiPresent when an external agent created the entry through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
idMail-register entry id (use with mail_register.get / .update).
4321matterThe matter the entry is filed against.
Fields of matter
idcase_numberdirectionincoming = received, outgoing = sent.
"incoming" "outgoing" datePostal/recorded date, YYYY-MM-DD (no time of day).
subjectsenderFeladó: the sender (free text).
toCímzett: the addressee (free text).
recipientFor an incoming mail: the display name of the colleague who received it. For an outgoing mail: the free-text addressee/received-by.
recipient_user_idSet only for an incoming mail whose recipient is a colleague (a user id); null for an outgoing mail.
electronicTrue when the item was recorded as electronic (type_of_mail).
delivery_method_idId of the delivery method (mail_delivery_methods).
delivery_methodLocalized delivery-method label (Posta, Cégkapu, Perkapu, ...), or the electronic/non-electronic fallback for a legacy row.
tracking_noAzonosító: tracking number.
folder_pathrecorded_by_user_idThe colleague who recorded the entry.
recorded_bycreated_atupdated_atPass this to mail_register.update as expected_updated_at.
created_via_apiPresent when an external agent created the entry through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/mail-register' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"matter_id": 1234,
"direction": "incoming",
"date": "2026-11-03",
"subject": "Fellebbezés a Kúriához"
}'
const response = await fetch("https://www.flowyer.com/api/agent/mail-register", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"matter_id": 1234,
"direction": "incoming",
"date": "2026-11-03",
"subject": "Fellebbezés a Kúriához"
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/mail-register",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"matter_id": 1234,
"direction": "incoming",
"date": "2026-11-03",
"subject": "Fellebbezés a Kúriához",
},
timeout=30,
)
print(response.status_code, response.json())
{
"matter_id": 1234,
"direction": "incoming",
"date": "2026-11-03",
"subject": "Fellebbezés a Kúriához"
}
Response samples
{
"data": {
"id": 4321,
"matter": {
"id": 1234,
"case_number": "string"
},
"direction": "incoming",
"date": "2026-10-30",
"subject": "string",
"sender": "string",
"to": "string",
"recipient": "string",
"recipient_user_id": 1234,
"electronic": true,
"delivery_method_id": 1234,
"delivery_method": "string",
"tracking_no": "string",
"folder_path": "string",
"recorded_by_user_id": 1234,
"recorded_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": true
}
{
"data": {
"id": 4321,
"matter": {
"id": 1234,
"case_number": "string"
},
"direction": "incoming",
"date": "2026-10-30",
"subject": "string",
"sender": "string",
"to": "string",
"recipient": "string",
"recipient_user_id": 1234,
"electronic": true,
"delivery_method_id": 1234,
"delivery_method": "string",
"tracking_no": "string",
"folder_path": "string",
"recorded_by_user_id": 1234,
"recorded_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"matter_id": [
"The matter id field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update mail register entry
/api/agent/mail-register/{id}
Updates a mail-register (Postakönyv) entry; only the fields you send are changed. The entry stays on its matter. Pass expected_updated_at from your last read; a conflicting change answers 409. Answers 404 when the entry is not visible.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
directionincoming = received, outgoing = sent.
"incoming" "outgoing" datePostal/recorded date, YYYY-MM-DD.
subjectSubject. Plain text; "<" and ">" are not allowed.
^[^<>]*$senderFeladó: the sender (free text). Plain text; "<" and ">" are not allowed.
^[^<>]*$toCímzett: the addressee (free text). Plain text; "<" and ">" are not allowed.
^[^<>]*$recipientOutgoing mail: the free-text addressee. Not allowed together with recipient_user_id. Plain text; "<" and ">" are not allowed.
^[^<>]*$recipient_user_idIncoming mail: the receiving colleague (a user id), stored as the numeric recipient. Not allowed together with recipient.
electronicWhether the item is electronic (type_of_mail).
delivery_method_idDelivery method id (mail_delivery_methods); 422 when unknown.
tracking_noAzonosító: tracking number. Plain text; "<" and ">" are not allowed.
^[^<>]*$folder_path^[^<>]*$Responses
200
Updated.
datarequiredFields of data
idMail-register entry id (use with mail_register.get / .update).
4321matterThe matter the entry is filed against.
Fields of matter
idcase_numberdirectionincoming = received, outgoing = sent.
"incoming" "outgoing" datePostal/recorded date, YYYY-MM-DD (no time of day).
subjectsenderFeladó: the sender (free text).
toCímzett: the addressee (free text).
recipientFor an incoming mail: the display name of the colleague who received it. For an outgoing mail: the free-text addressee/received-by.
recipient_user_idSet only for an incoming mail whose recipient is a colleague (a user id); null for an outgoing mail.
electronicTrue when the item was recorded as electronic (type_of_mail).
delivery_method_idId of the delivery method (mail_delivery_methods).
delivery_methodLocalized delivery-method label (Posta, Cégkapu, Perkapu, ...), or the electronic/non-electronic fallback for a legacy row.
tracking_noAzonosító: tracking number.
folder_pathrecorded_by_user_idThe colleague who recorded the entry.
recorded_bycreated_atupdated_atPass this to mail_register.update as expected_updated_at.
created_via_apiPresent when an external agent created the entry through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratupdatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/mail-register/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"direction": "incoming"
}'
const response = await fetch("https://www.flowyer.com/api/agent/mail-register/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"direction": "incoming"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/mail-register/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"direction": "incoming",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"direction": "incoming"
}
Response samples
{
"data": {
"id": 4321,
"matter": {
"id": 1234,
"case_number": "string"
},
"direction": "incoming",
"date": "2026-10-30",
"subject": "string",
"sender": "string",
"to": "string",
"recipient": "string",
"recipient_user_id": 1234,
"electronic": true,
"delivery_method_id": 1234,
"delivery_method": "string",
"tracking_no": "string",
"folder_path": "string",
"recorded_by_user_id": 1234,
"recorded_by": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Comments
List matter comments
/api/agent/matters/{id}/comments
Lists the comments of a matter, oldest first: id, author, body (rich HTML) and the parent_id of a reply. Flat, not nested - rebuild the one-level threads from parent_id. Pass top_level_only to exclude replies. Answers 404 when the matter is not visible to the user.
Path parameters
idrequiredMatter id (must be visible).
1234Query parameters
top_level_onlyOnly top-level comments (exclude replies). Default false.
falselimitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
id9876matter_idThe matter (= case_id) the comment belongs to.
parent_idThe top-level comment this replies to; null for a top-level comment. Replies are one level deep.
author_user_idUser id of the author (null for a legacy import).
author_nameDisplay name of the author, or null.
bodyThe comment as stored: rich HTML, already HTMLPurifier-cleaned for comments written through this API.
created_atWhen the comment was posted, ISO 8601 in the office zone.
updated_atOptimistic-lock token: pass it to comments.update as expected_updated_at. Not a display time; null for a legacy comment without one.
editedTrue when the comment has been edited since it was posted.
paginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/matters/1234/comments?limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/comments?limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/matters/1234/comments?limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 9876,
"matter_id": 1234,
"parent_id": 1234,
"author_user_id": 1234,
"author_name": "string",
"body": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"edited": true
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "matter_not_visible",
"message": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"message_localized": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"matter_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Post matter comment
/api/agent/matters/{id}/comments
Posts a comment on a matter, or a one-level reply (parent_id = a TOP-LEVEL comment of the SAME matter). The body is rich HTML, server-side sanitised before store. Answers 404 when the matter is not visible, 422 when parent_id is not a top-level comment of the matter.
Path parameters
idrequiredMatter id (must be visible).
1234Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
bodyrequiredThe comment, rich HTML (as TinyMCE produces). Server-side HTMLPurifier-cleaned before store. At most 100000 characters.
"<p>Egyetértek, a határidő tartható.</p>"parent_idReply to this TOP-LEVEL comment of the SAME matter (from comments.list, parent_id = null); omit for a new top-level comment.
idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
id9876matter_idThe matter (= case_id) the comment belongs to.
parent_idThe top-level comment this replies to; null for a top-level comment. Replies are one level deep.
author_user_idUser id of the author (null for a legacy import).
author_nameDisplay name of the author, or null.
bodyThe comment as stored: rich HTML, already HTMLPurifier-cleaned for comments written through this API.
created_atWhen the comment was posted, ISO 8601 in the office zone.
updated_atOptimistic-lock token: pass it to comments.update as expected_updated_at. Not a display time; null for a legacy comment without one.
editedTrue when the comment has been edited since it was posted.
createdtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
id9876matter_idThe matter (= case_id) the comment belongs to.
parent_idThe top-level comment this replies to; null for a top-level comment. Replies are one level deep.
author_user_idUser id of the author (null for a legacy import).
author_nameDisplay name of the author, or null.
bodyThe comment as stored: rich HTML, already HTMLPurifier-cleaned for comments written through this API.
created_atWhen the comment was posted, ISO 8601 in the office zone.
updated_atOptimistic-lock token: pass it to comments.update as expected_updated_at. Not a display time; null for a legacy comment without one.
editedTrue when the comment has been edited since it was posted.
createdfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/matters/1234/comments' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"body": "<p>Egyetértek, a határidő tartható.</p>"
}'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/comments", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"body": "<p>Egyetértek, a határidő tartható.</p>"
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/matters/1234/comments",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"body": "<p>Egyetértek, a határidő tartható.</p>",
},
timeout=30,
)
print(response.status_code, response.json())
{
"body": "<p>Egyetértek, a határidő tartható.</p>"
}
Response samples
{
"data": {
"id": 9876,
"matter_id": 1234,
"parent_id": 1234,
"author_user_id": 1234,
"author_name": "string",
"body": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"edited": true
},
"created": true
}
{
"data": {
"id": 9876,
"matter_id": 1234,
"parent_id": 1234,
"author_user_id": 1234,
"author_name": "string",
"body": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"edited": true
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"body": [
"The body field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Edit own matter comment
/api/agent/comments/{id}
Edits one of your OWN comments (its body only; rich HTML, server-side sanitised before store). Pass expected_updated_at from your last read; a conflicting change answers 409. Answers 404 for a comment that is not yours or on a matter you cannot see.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
bodyrequiredNew comment body, rich HTML (as TinyMCE produces). Server-side HTMLPurifier-cleaned before store. At most 100000 characters.
"<p>Pontosítás: a határidő november 15.</p>"Responses
200
Updated.
datarequiredFields of data
id9876matter_idThe matter (= case_id) the comment belongs to.
parent_idThe top-level comment this replies to; null for a top-level comment. Replies are one level deep.
author_user_idUser id of the author (null for a legacy import).
author_nameDisplay name of the author, or null.
bodyThe comment as stored: rich HTML, already HTMLPurifier-cleaned for comments written through this API.
created_atWhen the comment was posted, ISO 8601 in the office zone.
updated_atOptimistic-lock token: pass it to comments.update as expected_updated_at. Not a display time; null for a legacy comment without one.
editedTrue when the comment has been edited since it was posted.
updatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/comments/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"body": "<p>Pontosítás: a határidő november 15.</p>"
}'
const response = await fetch("https://www.flowyer.com/api/agent/comments/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"body": "<p>Pontosítás: a határidő november 15.</p>"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/comments/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"body": "<p>Pontosítás: a határidő november 15.</p>",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"body": "<p>Pontosítás: a határidő november 15.</p>"
}
Response samples
{
"data": {
"id": 9876,
"matter_id": 1234,
"parent_id": 1234,
"author_user_id": 1234,
"author_name": "string",
"body": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"edited": true
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Trials
List matter hearings
/api/agent/matters/{id}/trials
Lists the court hearings (tárgyalások) of a matter: date, court, place, the attending colleagues and the judge, newest first. Optionally filtered by a from/to day range or by updated_since. Answers 404 when the matter is not visible to the user.
Path parameters
idrequiredMatter id.
1234Query parameters
fromOnly hearings on or after this day (YYYY-MM-DD), in the office time zone.
"2026-01-01"toOnly hearings on or before this day (YYYY-MM-DD), in the office time zone.
"2026-12-31"updated_sinceOnly hearings changed at or after this ISO 8601 time.
limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
id4321matterThe matter (case) the hearing belongs to.
Fields of matter
idcase_numberdateHearing date and time, ISO 8601 in the office time zone. It is stored in UTC, so compare instants rather than the wall-clock text.
"2026-03-02T09:00:00+01:00"participationWhether a colleague's attendance is required: "required", "not_required", or null when unset.
"required" "not_required" courtThe court of the hearing, resolved from the shared court registry; null when no court is set.
Fields of court
idnamecityzipaddresssourceAlways "registry": the court is a reference to the shared court registry (courts.list), not free text.
placeRoom or location of the hearing. Plain text.
name_of_the_judgeName of the judge. Plain text.
notesFree-text notes on the hearing. Plain text.
responsiblesColleagues attending the hearing (ids from colleagues.list).
Fields of responsibles
user_idnamerecorded_byThe colleague who recorded the hearing.
Fields of recorded_by
user_idnamecreated_atupdated_atPass this to trials.update as expected_updated_at.
created_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/matters/1234/trials?from=2026-01-01&to=2026-12-31&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/trials?from=2026-01-01&to=2026-12-31&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/matters/1234/trials?from=2026-01-01&to=2026-12-31&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 4321,
"matter": {
"id": 1234,
"case_number": "string"
},
"date": "2026-03-02T09:00:00+01:00",
"participation": "required",
"court": {
"id": 1234,
"name": "string",
"city": "string",
"zip": "string",
"address": "string",
"source": "string"
},
"place": "string",
"name_of_the_judge": "string",
"notes": "string",
"responsibles": [
{
"user_id": 1234,
"name": "string"
}
],
"recorded_by": {
"user_id": 1234,
"name": "string"
},
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "matter_not_visible",
"message": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"message_localized": "The matter 1234 does not exist, is archived, or is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"matter_id": 1234
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get hearing
/api/agent/trials/{id}
Returns one court hearing (tárgyalás) with its matter, court, attending colleagues and the updated_at lock token for trials.update. Answers 404 when the hearing does not exist or the user may not see its matter.
Path parameters
idrequiredHearing id.
4321Responses
200
OK.
datarequiredFields of data
id4321matterThe matter (case) the hearing belongs to.
Fields of matter
idcase_numberdateHearing date and time, ISO 8601 in the office time zone. It is stored in UTC, so compare instants rather than the wall-clock text.
"2026-03-02T09:00:00+01:00"participationWhether a colleague's attendance is required: "required", "not_required", or null when unset.
"required" "not_required" courtThe court of the hearing, resolved from the shared court registry; null when no court is set.
Fields of court
idnamecityzipaddresssourceAlways "registry": the court is a reference to the shared court registry (courts.list), not free text.
placeRoom or location of the hearing. Plain text.
name_of_the_judgeName of the judge. Plain text.
notesFree-text notes on the hearing. Plain text.
responsiblesColleagues attending the hearing (ids from colleagues.list).
Fields of responsibles
user_idnamerecorded_byThe colleague who recorded the hearing.
Fields of recorded_by
user_idnamecreated_atupdated_atPass this to trials.update as expected_updated_at.
created_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserat
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/trials/4321' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/trials/4321", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/trials/4321",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 4321,
"matter": {
"id": 1234,
"case_number": "string"
},
"date": "2026-03-02T09:00:00+01:00",
"participation": "required",
"court": {
"id": 1234,
"name": "string",
"city": "string",
"zip": "string",
"address": "string",
"source": "string"
},
"place": "string",
"name_of_the_judge": "string",
"notes": "string",
"responsibles": [
{
"user_id": 1234,
"name": "string"
}
],
"recorded_by": {
"user_id": 1234,
"name": "string"
},
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"record_type": "trial",
"record_id": 4321
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Create hearing
/api/agent/matters/{id}/trials
Records a court hearing (tárgyalás) on a matter: time, an optional court (court_id from courts.list), the room, judge, notes and attending colleagues. The matter must be visible and not archived; attendees are notified. A time with no offset is read in the office zone.
Path parameters
idrequiredMatter the hearing belongs to (must be visible and not archived).
1234Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
daterequiredHearing date and time, ISO 8601. With an offset it is honoured; without one it is read in the office time zone. Stored in UTC.
"2026-03-02T09:00:00+01:00"court_idCourt of the hearing: an id from courts.list (the shared court registry). Omit when the court is not known.
42participationWhether a colleague's attendance is required. Omit to leave it unset.
"required" "not_required" placeRoom or location of the hearing. Plain text; "<" and ">" are not allowed.
^[^<>]*$"II. emelet 205."name_of_the_judgeName of the judge. Plain text; "<" and ">" are not allowed.
^[^<>]*$notesFree-text notes on the hearing. Plain text; "<" and ">" are not allowed.
^[^<>]*$responsible_user_idsColleague user ids (from colleagues.list) attending the hearing.
idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
id4321matterThe matter (case) the hearing belongs to.
Fields of matter
idcase_numberdateHearing date and time, ISO 8601 in the office time zone. It is stored in UTC, so compare instants rather than the wall-clock text.
"2026-03-02T09:00:00+01:00"participationWhether a colleague's attendance is required: "required", "not_required", or null when unset.
"required" "not_required" courtThe court of the hearing, resolved from the shared court registry; null when no court is set.
Fields of court
idnamecityzipaddresssourceAlways "registry": the court is a reference to the shared court registry (courts.list), not free text.
placeRoom or location of the hearing. Plain text.
name_of_the_judgeName of the judge. Plain text.
notesFree-text notes on the hearing. Plain text.
responsiblesColleagues attending the hearing (ids from colleagues.list).
Fields of responsibles
user_idnamerecorded_byThe colleague who recorded the hearing.
Fields of recorded_by
user_idnamecreated_atupdated_atPass this to trials.update as expected_updated_at.
created_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
id4321matterThe matter (case) the hearing belongs to.
Fields of matter
idcase_numberdateHearing date and time, ISO 8601 in the office time zone. It is stored in UTC, so compare instants rather than the wall-clock text.
"2026-03-02T09:00:00+01:00"participationWhether a colleague's attendance is required: "required", "not_required", or null when unset.
"required" "not_required" courtThe court of the hearing, resolved from the shared court registry; null when no court is set.
Fields of court
idnamecityzipaddresssourceAlways "registry": the court is a reference to the shared court registry (courts.list), not free text.
placeRoom or location of the hearing. Plain text.
name_of_the_judgeName of the judge. Plain text.
notesFree-text notes on the hearing. Plain text.
responsiblesColleagues attending the hearing (ids from colleagues.list).
Fields of responsibles
user_idnamerecorded_byThe colleague who recorded the hearing.
Fields of recorded_by
user_idnamecreated_atupdated_atPass this to trials.update as expected_updated_at.
created_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/matters/1234/trials' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"date": "2026-03-02T09:00:00+01:00",
"court_id": 42,
"place": "II. emelet 205."
}'
const response = await fetch("https://www.flowyer.com/api/agent/matters/1234/trials", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"date": "2026-03-02T09:00:00+01:00",
"court_id": 42,
"place": "II. emelet 205."
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/matters/1234/trials",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"date": "2026-03-02T09:00:00+01:00",
"court_id": 42,
"place": "II. emelet 205.",
},
timeout=30,
)
print(response.status_code, response.json())
{
"date": "2026-03-02T09:00:00+01:00",
"court_id": 42,
"place": "II. emelet 205."
}
Response samples
{
"data": {
"id": 4321,
"matter": {
"id": 1234,
"case_number": "string"
},
"date": "2026-03-02T09:00:00+01:00",
"participation": "required",
"court": {
"id": 1234,
"name": "string",
"city": "string",
"zip": "string",
"address": "string",
"source": "string"
},
"place": "string",
"name_of_the_judge": "string",
"notes": "string",
"responsibles": [
{
"user_id": 1234,
"name": "string"
}
],
"recorded_by": {
"user_id": 1234,
"name": "string"
},
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": true
}
{
"data": {
"id": 4321,
"matter": {
"id": 1234,
"case_number": "string"
},
"date": "2026-03-02T09:00:00+01:00",
"participation": "required",
"court": {
"id": 1234,
"name": "string",
"city": "string",
"zip": "string",
"address": "string",
"source": "string"
},
"place": "string",
"name_of_the_judge": "string",
"notes": "string",
"responsibles": [
{
"user_id": 1234,
"name": "string"
}
],
"recorded_by": {
"user_id": 1234,
"name": "string"
},
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"date": [
"The date field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update hearing
/api/agent/trials/{id}
Updates a court hearing (tárgyalás): its time, court, place, judge, notes, attendance flag and attendees. Writes only the fields you send; send responsible_user_ids to replace the whole attendee list. Pass expected_updated_at from your last read; a conflicting change answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
dateNew hearing date and time, ISO 8601 (an offset is honoured, a naive time is read in the office zone). Stored in UTC.
"2026-03-02T09:00:00+01:00"court_idNew court id from courts.list; null to clear the court.
participationWhether a colleague's attendance is required; null to clear it.
"required" "not_required" placeRoom or location. Plain text; "<" and ">" are not allowed. "" clears it.
^[^<>]*$name_of_the_judgeName of the judge. Plain text; "<" and ">" are not allowed. "" clears it.
^[^<>]*$notesFree-text notes. Plain text; "<" and ">" are not allowed. "" clears it.
^[^<>]*$responsible_user_idsFull replacement set of attendee user ids (from colleagues.list). Omit to leave the attendees unchanged.
Responses
200
Updated.
datarequiredFields of data
id4321matterThe matter (case) the hearing belongs to.
Fields of matter
idcase_numberdateHearing date and time, ISO 8601 in the office time zone. It is stored in UTC, so compare instants rather than the wall-clock text.
"2026-03-02T09:00:00+01:00"participationWhether a colleague's attendance is required: "required", "not_required", or null when unset.
"required" "not_required" courtThe court of the hearing, resolved from the shared court registry; null when no court is set.
Fields of court
idnamecityzipaddresssourceAlways "registry": the court is a reference to the shared court registry (courts.list), not free text.
placeRoom or location of the hearing. Plain text.
name_of_the_judgeName of the judge. Plain text.
notesFree-text notes on the hearing. Plain text.
responsiblesColleagues attending the hearing (ids from colleagues.list).
Fields of responsibles
user_idnamerecorded_byThe colleague who recorded the hearing.
Fields of recorded_by
user_idnamecreated_atupdated_atPass this to trials.update as expected_updated_at.
created_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratupdatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/trials/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-03-02T09:00:00+01:00"
}'
const response = await fetch("https://www.flowyer.com/api/agent/trials/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-03-02T09:00:00+01:00"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/trials/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-03-02T09:00:00+01:00",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-03-02T09:00:00+01:00"
}
Response samples
{
"data": {
"id": 4321,
"matter": {
"id": 1234,
"case_number": "string"
},
"date": "2026-03-02T09:00:00+01:00",
"participation": "required",
"court": {
"id": 1234,
"name": "string",
"city": "string",
"zip": "string",
"address": "string",
"source": "string"
},
"place": "string",
"name_of_the_judge": "string",
"notes": "string",
"responsibles": [
{
"user_id": 1234,
"name": "string"
}
],
"recorded_by": {
"user_id": 1234,
"name": "string"
},
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Courts
List courts
/api/agent/courts
Lists the shared court registry (id, name, city) for the court_id of trials.create / trials.update. With q (a court name as heard; accents, casing and small typos tolerated) answers status exact/similar/none: only "exact" is safe to use directly. It is the Hungarian court directory.
Query parameters
qA court name to look up, as heard or dictated, in any word order. Omit it to list every court.
"Fővárosi Törvényszék"limitMaximum courts to return (default 200).
200Responses
200
OK.
datarequiredFields of data
statusOnly with q: exact = one confident match (safe to use its id), similar = approximate or several matches (confirm with the user first), none = no match (ask the user). Null without q.
"exact" "similar" "none" courtsCourts, best match first with q, otherwise by name.
Fields of courts
idCourt id: pass it as court_id to trials.create / trials.update.
nameCourt name as shown in Flowyer.
cityzipaddresstotalHow many courts matched (all courts without q) before the list was cut to limit.
truncatedTrue when more courts matched than were returned: narrow q or raise limit.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/courts?q=F%C5%91v%C3%A1rosi%20T%C3%B6rv%C3%A9nysz%C3%A9k' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/courts?q=F%C5%91v%C3%A1rosi%20T%C3%B6rv%C3%A9nysz%C3%A9k", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/courts?q=F%C5%91v%C3%A1rosi%20T%C3%B6rv%C3%A9nysz%C3%A9k",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"status": "exact",
"courts": [
{
"id": 1234,
"name": "string",
"city": "string",
"zip": "string",
"address": "string"
}
],
"total": 1,
"truncated": true
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"courts:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"courts:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "courts:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Hr
List staff leaves
/api/agent/hr/leaves
Lists approved staff leaves (who is or will be on leave): the colleague, start/end dates, working-day count, a privacy-redacted category (leave/unpaid; sick is merged into leave and never identifiable) and any substitute. Never returns the leave type, notes or who approved it.
Query parameters
user_idOnly this colleague's leaves. A central user id (from colleagues.list), matched to the employee behind the leave.
1234on_dateWho is on leave on this exact day (start_date <= on_date <= end_date). When set, it overrides date_from, date_to and timeframe.
date_fromOnly leaves still running on or after this date (end_date >= date_from). Defaults to today, so fully past leaves are excluded by default.
date_toOnly leaves starting on or before this date (start_date <= date_to). Omit for open-ended future.
timeframeExtra start-date constraint versus today: current = already started (ongoing), upcoming = not yet started, all = no extra. Ignored when on_date is set.
"current" "upcoming" "all" "all"limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idLeave request id (a handle only; not sensitive).
user_idThe colleague on leave (central user id).
userThe colleague's display name.
start_dateFirst day of leave (YYYY-MM-DD).
end_dateLast day of leave, inclusive (YYYY-MM-DD).
days_countWorking days of leave. Does not reveal the type.
categoryPrivacy-redacted leave type: annual and sick both become "leave" (sick is never identifiable), unpaid stays "unpaid". The raw leave type is never exposed.
"leave" "unpaid" statusAlways "approved" - only approved leaves are returned; pending, rejected and cancelled never surface.
"approved" substitute_user_idCovering colleague for this leave (central user id), or null.
substitute_userCovering colleague's display name, or null.
paginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/hr/leaves?user_id=1234&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/hr/leaves?user_id=1234&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/hr/leaves?user_id=1234&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 1234,
"user_id": 1234,
"user": "string",
"start_date": "2026-10-30",
"end_date": "2026-10-30",
"days_count": 0,
"category": "leave",
"status": "approved",
"substitute_user_id": 1234,
"substitute_user": "string"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"hr:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"hr:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "hr:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Calendar
List calendar events
/api/agent/calendar/events
Lists calendar events overlapping a [from, to] date range: the office calendar, your own and calendars shared with you. Recurring events are returned once (is_recurring), NOT expanded. Ordered by start. A personal event not shared with you never appears.
Query parameters
fromrequiredRange start (inclusive), ISO 8601. Events overlapping [from, to] are returned. A time with no offset is read in the office zone.
"2026-03-01T00:00:00+01:00"torequiredRange end (inclusive), ISO 8601. Must be on or after from (else 422) and at most 366 days after it.
"2026-03-31T23:59:59+01:00"calendar_idsNarrow to these container calendar ids; each is intersected with the calendars you may view, so an id you cannot see is silently dropped. Omit for every visible calendar.
include_recurringInclude recurring master events whose rule overlaps the range (returned un-expanded, is_recurring = true). Default true.
truelimitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
id5501calendarThe calendar the event belongs to.
Fields of calendar
idContainer calendar id, or null for the implicit shared office calendar (a legacy event with no container).
nametype"office" = the shared office calendar (every office user may view and edit it); "personal" = a user's own calendar, visible only to its owner and those it is shared with.
"office" "personal" ownerOwner of a personal calendar; null for the office calendar.
Fields of owner
user_idnametitleEvent title. Plain text.
descriptionEvent description. Plain text.
locationLocation. Plain text.
participantsParticipants, free text. Plain text.
startAn all-day event: the start date "YYYY-MM-DD". A timed event: ISO 8601 in the office time zone (the stored UTC instant).
"2026-03-02T09:00:00+01:00"endSame encoding as start. For an all-day event the end date is INCLUSIVE (the stored value, not the +1-day value a calendar grid uses). Null when the event has no end.
all_dayTrue for an all-day event (start/end are bare dates); false for a timed event (start/end are ISO 8601 instants).
colorDisplay colour, e.g. "#2c7be5".
is_recurringTrue when the event is a recurring master. Recurring events are returned once, NOT expanded into occurrences, and cannot be modified through calendar.events.update.
recurrenceThe recurrence rule of a recurring master, or null for a single event. Read-only.
Fields of recurrence
freqdaily | weekly | monthly | yearly.
intervalbyweekdayComma-separated weekday codes (e.g. "MO,WE,FR").
untilcountcreated_byThe colleague who created the event.
Fields of created_by
user_idnamecreated_atupdated_atPass this to calendar.events.update as expected_updated_at.
created_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/calendar/events?from=2026-03-01T00%3A00%3A00%2B01%3A00&to=2026-03-31T23%3A59%3A59%2B01%3A00&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/calendar/events?from=2026-03-01T00%3A00%3A00%2B01%3A00&to=2026-03-31T23%3A59%3A59%2B01%3A00&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/calendar/events?from=2026-03-01T00%3A00%3A00%2B01%3A00&to=2026-03-31T23%3A59%3A59%2B01%3A00&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 5501,
"calendar": {
"id": 1234,
"name": "string",
"type": "office",
"owner": {
"user_id": 1234,
"name": "string"
}
},
"title": "string",
"description": "string",
"location": "string",
"participants": "string",
"start": "2026-03-02T09:00:00+01:00",
"end": "string",
"all_day": true,
"color": "string",
"is_recurring": true,
"recurrence": {
"freq": "string",
"interval": 0,
"byweekday": "string",
"until": "string",
"count": 0
},
"created_by": {
"user_id": 1234,
"name": "string"
},
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"calendar:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"calendar:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "calendar:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get calendar event
/api/agent/calendar/events/{id}
Returns one calendar event with its calendar, recurrence summary and the updated_at lock token for calendar.events.update. Answers 404 when the event does not exist or its calendar is not visible to the user.
Path parameters
idrequiredEvent id.
5501Responses
200
OK.
datarequiredFields of data
id5501calendarThe calendar the event belongs to.
Fields of calendar
idContainer calendar id, or null for the implicit shared office calendar (a legacy event with no container).
nametype"office" = the shared office calendar (every office user may view and edit it); "personal" = a user's own calendar, visible only to its owner and those it is shared with.
"office" "personal" ownerOwner of a personal calendar; null for the office calendar.
Fields of owner
user_idnametitleEvent title. Plain text.
descriptionEvent description. Plain text.
locationLocation. Plain text.
participantsParticipants, free text. Plain text.
startAn all-day event: the start date "YYYY-MM-DD". A timed event: ISO 8601 in the office time zone (the stored UTC instant).
"2026-03-02T09:00:00+01:00"endSame encoding as start. For an all-day event the end date is INCLUSIVE (the stored value, not the +1-day value a calendar grid uses). Null when the event has no end.
all_dayTrue for an all-day event (start/end are bare dates); false for a timed event (start/end are ISO 8601 instants).
colorDisplay colour, e.g. "#2c7be5".
is_recurringTrue when the event is a recurring master. Recurring events are returned once, NOT expanded into occurrences, and cannot be modified through calendar.events.update.
recurrenceThe recurrence rule of a recurring master, or null for a single event. Read-only.
Fields of recurrence
freqdaily | weekly | monthly | yearly.
intervalbyweekdayComma-separated weekday codes (e.g. "MO,WE,FR").
untilcountcreated_byThe colleague who created the event.
Fields of created_by
user_idnamecreated_atupdated_atPass this to calendar.events.update as expected_updated_at.
created_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserat
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/calendar/events/5501' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/calendar/events/5501", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/calendar/events/5501",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 5501,
"calendar": {
"id": 1234,
"name": "string",
"type": "office",
"owner": {
"user_id": 1234,
"name": "string"
}
},
"title": "string",
"description": "string",
"location": "string",
"participants": "string",
"start": "2026-03-02T09:00:00+01:00",
"end": "string",
"all_day": true,
"color": "string",
"is_recurring": true,
"recurrence": {
"freq": "string",
"interval": 0,
"byweekday": "string",
"until": "string",
"count": 0
},
"created_by": {
"user_id": 1234,
"name": "string"
},
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"record_type": "calendar_event",
"record_id": 5501
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Create calendar event
/api/agent/calendar/events
Creates a single calendar event (timed or all-day) on the office calendar or a calendar you may edit. A timed start/end honours an ISO 8601 offset, a naive time is read in the office zone; an all-day end date is inclusive. Recurring events are not created here. Not pushed to external calendars.
Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
titlerequiredEvent title. Plain text; "<" and ">" are not allowed (it flows unescaped into the reminder notification).
^[^<>]*$"Ügyfél egyeztetés"startrequiredAll-day (all_day true): a date "YYYY-MM-DD". Timed (all_day false): ISO 8601; an offset is honoured, a naive time is read in the office zone.
"2026-03-02T09:00:00+01:00"endSame encoding as start. Default = start. For an all-day event the end date is INCLUSIVE. Must not be before start (else 422).
all_dayWhether the event is all-day. Default false (timed).
falsecalendar_idTarget container calendar; default = the office calendar. You must hold edit rights on it (else 403). Only on a tenant with the personal-calendar feature.
locationLocation. Plain text; "<" and ">" are not allowed.
^[^<>]*$descriptionDescription, plain text (not HTML); "<" and ">" are not allowed.
^[^<>]*$participantsParticipants, free text. Plain text; "<" and ">" are not allowed.
^[^<>]*$colorDisplay colour, e.g. "#2c7be5".
^#?[A-Za-z0-9_-]{1,31}$idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
id5501calendarThe calendar the event belongs to.
Fields of calendar
idContainer calendar id, or null for the implicit shared office calendar (a legacy event with no container).
nametype"office" = the shared office calendar (every office user may view and edit it); "personal" = a user's own calendar, visible only to its owner and those it is shared with.
"office" "personal" ownerOwner of a personal calendar; null for the office calendar.
Fields of owner
user_idnametitleEvent title. Plain text.
descriptionEvent description. Plain text.
locationLocation. Plain text.
participantsParticipants, free text. Plain text.
startAn all-day event: the start date "YYYY-MM-DD". A timed event: ISO 8601 in the office time zone (the stored UTC instant).
"2026-03-02T09:00:00+01:00"endSame encoding as start. For an all-day event the end date is INCLUSIVE (the stored value, not the +1-day value a calendar grid uses). Null when the event has no end.
all_dayTrue for an all-day event (start/end are bare dates); false for a timed event (start/end are ISO 8601 instants).
colorDisplay colour, e.g. "#2c7be5".
is_recurringTrue when the event is a recurring master. Recurring events are returned once, NOT expanded into occurrences, and cannot be modified through calendar.events.update.
recurrenceThe recurrence rule of a recurring master, or null for a single event. Read-only.
Fields of recurrence
freqdaily | weekly | monthly | yearly.
intervalbyweekdayComma-separated weekday codes (e.g. "MO,WE,FR").
untilcountcreated_byThe colleague who created the event.
Fields of created_by
user_idnamecreated_atupdated_atPass this to calendar.events.update as expected_updated_at.
created_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
id5501calendarThe calendar the event belongs to.
Fields of calendar
idContainer calendar id, or null for the implicit shared office calendar (a legacy event with no container).
nametype"office" = the shared office calendar (every office user may view and edit it); "personal" = a user's own calendar, visible only to its owner and those it is shared with.
"office" "personal" ownerOwner of a personal calendar; null for the office calendar.
Fields of owner
user_idnametitleEvent title. Plain text.
descriptionEvent description. Plain text.
locationLocation. Plain text.
participantsParticipants, free text. Plain text.
startAn all-day event: the start date "YYYY-MM-DD". A timed event: ISO 8601 in the office time zone (the stored UTC instant).
"2026-03-02T09:00:00+01:00"endSame encoding as start. For an all-day event the end date is INCLUSIVE (the stored value, not the +1-day value a calendar grid uses). Null when the event has no end.
all_dayTrue for an all-day event (start/end are bare dates); false for a timed event (start/end are ISO 8601 instants).
colorDisplay colour, e.g. "#2c7be5".
is_recurringTrue when the event is a recurring master. Recurring events are returned once, NOT expanded into occurrences, and cannot be modified through calendar.events.update.
recurrenceThe recurrence rule of a recurring master, or null for a single event. Read-only.
Fields of recurrence
freqdaily | weekly | monthly | yearly.
intervalbyweekdayComma-separated weekday codes (e.g. "MO,WE,FR").
untilcountcreated_byThe colleague who created the event.
Fields of created_by
user_idnamecreated_atupdated_atPass this to calendar.events.update as expected_updated_at.
created_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/calendar/events' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"title": "Ügyfél egyeztetés",
"start": "2026-03-02T09:00:00+01:00"
}'
const response = await fetch("https://www.flowyer.com/api/agent/calendar/events", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"title": "Ügyfél egyeztetés",
"start": "2026-03-02T09:00:00+01:00"
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/calendar/events",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"title": "Ügyfél egyeztetés",
"start": "2026-03-02T09:00:00+01:00",
},
timeout=30,
)
print(response.status_code, response.json())
{
"title": "Ügyfél egyeztetés",
"start": "2026-03-02T09:00:00+01:00"
}
Response samples
{
"data": {
"id": 5501,
"calendar": {
"id": 1234,
"name": "string",
"type": "office",
"owner": {
"user_id": 1234,
"name": "string"
}
},
"title": "string",
"description": "string",
"location": "string",
"participants": "string",
"start": "2026-03-02T09:00:00+01:00",
"end": "string",
"all_day": true,
"color": "string",
"is_recurring": true,
"recurrence": {
"freq": "string",
"interval": 0,
"byweekday": "string",
"until": "string",
"count": 0
},
"created_by": {
"user_id": 1234,
"name": "string"
},
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": true
}
{
"data": {
"id": 5501,
"calendar": {
"id": 1234,
"name": "string",
"type": "office",
"owner": {
"user_id": 1234,
"name": "string"
}
},
"title": "string",
"description": "string",
"location": "string",
"participants": "string",
"start": "2026-03-02T09:00:00+01:00",
"end": "string",
"all_day": true,
"color": "string",
"is_recurring": true,
"recurrence": {
"freq": "string",
"interval": 0,
"byweekday": "string",
"until": "string",
"count": 0
},
"created_by": {
"user_id": 1234,
"name": "string"
},
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"title": [
"The title field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update calendar event
/api/agent/calendar/events/{id}
Updates a single calendar event: title, start/end, all_day, location, description, participants, colour. Writes only the fields you send; "" clears location/description/participants. A recurring event answers 422. Pass expected_updated_at from your last read; a conflicting change answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
titleNew title. Plain text; "<" and ">" are not allowed.
^[^<>]*$startNew start. All-day: "YYYY-MM-DD". Timed: ISO 8601 (an offset honoured, a naive time read in the office zone). Re-encoded if all_day changes.
endNew end, same encoding as start; "" clears it. For an all-day event the end date is inclusive. Must not be before start (else 422).
all_dayIf sent, start/end are re-encoded accordingly; if omitted, the row's current all_day is used.
locationLocation. Plain text; "<" and ">" are not allowed. "" clears it.
^[^<>]*$descriptionDescription, plain text (not HTML); "<" and ">" are not allowed. "" clears it.
^[^<>]*$participantsParticipants, free text. Plain text; "<" and ">" are not allowed. "" clears it.
^[^<>]*$colorDisplay colour, e.g. "#2c7be5". "" clears it.
^#?[A-Za-z0-9_-]{1,31}$Responses
200
Updated.
datarequiredFields of data
id5501calendarThe calendar the event belongs to.
Fields of calendar
idContainer calendar id, or null for the implicit shared office calendar (a legacy event with no container).
nametype"office" = the shared office calendar (every office user may view and edit it); "personal" = a user's own calendar, visible only to its owner and those it is shared with.
"office" "personal" ownerOwner of a personal calendar; null for the office calendar.
Fields of owner
user_idnametitleEvent title. Plain text.
descriptionEvent description. Plain text.
locationLocation. Plain text.
participantsParticipants, free text. Plain text.
startAn all-day event: the start date "YYYY-MM-DD". A timed event: ISO 8601 in the office time zone (the stored UTC instant).
"2026-03-02T09:00:00+01:00"endSame encoding as start. For an all-day event the end date is INCLUSIVE (the stored value, not the +1-day value a calendar grid uses). Null when the event has no end.
all_dayTrue for an all-day event (start/end are bare dates); false for a timed event (start/end are ISO 8601 instants).
colorDisplay colour, e.g. "#2c7be5".
is_recurringTrue when the event is a recurring master. Recurring events are returned once, NOT expanded into occurrences, and cannot be modified through calendar.events.update.
recurrenceThe recurrence rule of a recurring master, or null for a single event. Read-only.
Fields of recurrence
freqdaily | weekly | monthly | yearly.
intervalbyweekdayComma-separated weekday codes (e.g. "MO,WE,FR").
untilcountcreated_byThe colleague who created the event.
Fields of created_by
user_idnamecreated_atupdated_atPass this to calendar.events.update as expected_updated_at.
created_via_apiPresent when an external agent created the record through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratupdatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/calendar/events/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "string"
}'
const response = await fetch("https://www.flowyer.com/api/agent/calendar/events/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "string"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/calendar/events/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "string",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"title": "string"
}
Response samples
{
"data": {
"id": 5501,
"calendar": {
"id": 1234,
"name": "string",
"type": "office",
"owner": {
"user_id": 1234,
"name": "string"
}
},
"title": "string",
"description": "string",
"location": "string",
"participants": "string",
"start": "2026-03-02T09:00:00+01:00",
"end": "string",
"all_day": true,
"color": "string",
"is_recurring": true,
"recurrence": {
"freq": "string",
"interval": 0,
"byweekday": "string",
"until": "string",
"count": 0
},
"created_by": {
"user_id": 1234,
"name": "string"
},
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Bookings
List bookings
/api/agent/bookings
Lists the office's appointment bookings (időpontfoglalások), newest first. Optionally filtered by a from/to day range (booking time zone), a lawyer (user_id), a status or updated_since. Every key-holder sees all lawyers' bookings.
Query parameters
fromOnly bookings on or after this day (YYYY-MM-DD), matched literally against the booking-zone date.
"2026-01-01"toOnly bookings on or before this day (YYYY-MM-DD).
"2026-12-31"user_idFilter to one lawyer (user_id from colleagues.list). Omit for every lawyer of the office.
statusFilter by status.
"pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired" updated_sinceOnly bookings changed at or after this ISO 8601 time (updated_at is office-zone naive).
limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
id4321booking_numberHuman booking number, e.g. "#B-0001".
statuspending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.
"pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired" sourceWhere the booking came from. An API-created booking is "manual".
"flowyer" "website" "embed_widget" "manual" lawyerThe lawyer (user_id from colleagues.list) the appointment is with.
Fields of lawyer
user_idnamedateCalendar date in the booking time zone (YYYY-MM-DD), stored literally.
start_timeStart time HH:MM in the booking time zone.
end_timeEnd time HH:MM in the booking time zone.
startUnambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.
endUnambiguous ISO 8601 instant of the end.
timezoneThe booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.
location_preferenceWhether the consultation is in the office or online.
"office" "online" meeting_linkOnline-meeting URL, when set.
clientThe external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.
Fields of client
nameemailphonedescriptiongdpr_consentWhether the client's data-handling consent is recorded.
gdpr_consent_atWhen the consent was recorded.
cancel_reasonReason recorded on cancellation.
paymentRead-only payment state. The API never changes payment; booking fees are handled in Flowyer.
Fields of payment
status"none" "awaiting" "partial" "paid" "refunded" method"card" "transfer" amountcurrencyinvoice_numberconfirmed_atcancelled_atcreated_atupdated_atPass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.
created_via_apiPresent when an external agent created the booking through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/bookings?from=2026-01-01&to=2026-12-31&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/bookings?from=2026-01-01&to=2026-12-31&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/bookings?from=2026-01-01&to=2026-12-31&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 4321,
"booking_number": "string",
"status": "pending",
"source": "flowyer",
"lawyer": {
"user_id": 1234,
"name": "string"
},
"date": "2026-10-30",
"start_time": "string",
"end_time": "string",
"start": "2026-10-30T09:15:00+01:00",
"end": "2026-10-30T09:15:00+01:00",
"timezone": "string",
"location_preference": "office",
"meeting_link": "string",
"client": {
"name": "string",
"email": "info@example.hu",
"phone": "string",
"description": "string"
},
"gdpr_consent": true,
"gdpr_consent_at": "2026-10-30T09:15:00+01:00",
"cancel_reason": "string",
"payment": {
"status": "none",
"method": "card",
"amount": 0,
"currency": "string",
"invoice_number": "string"
},
"confirmed_at": "2026-10-30T09:15:00+01:00",
"cancelled_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"bookings:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"bookings:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "bookings:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get booking
/api/agent/bookings/{id}
Returns one appointment booking with the lawyer, the client's details, the start/end instants, payment state and the updated_at lock token for bookings.update / .confirm / .cancel. Answers 404 when the booking does not exist in this office.
Path parameters
idrequiredBooking id.
4321Responses
200
OK.
datarequiredFields of data
id4321booking_numberHuman booking number, e.g. "#B-0001".
statuspending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.
"pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired" sourceWhere the booking came from. An API-created booking is "manual".
"flowyer" "website" "embed_widget" "manual" lawyerThe lawyer (user_id from colleagues.list) the appointment is with.
Fields of lawyer
user_idnamedateCalendar date in the booking time zone (YYYY-MM-DD), stored literally.
start_timeStart time HH:MM in the booking time zone.
end_timeEnd time HH:MM in the booking time zone.
startUnambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.
endUnambiguous ISO 8601 instant of the end.
timezoneThe booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.
location_preferenceWhether the consultation is in the office or online.
"office" "online" meeting_linkOnline-meeting URL, when set.
clientThe external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.
Fields of client
nameemailphonedescriptiongdpr_consentWhether the client's data-handling consent is recorded.
gdpr_consent_atWhen the consent was recorded.
cancel_reasonReason recorded on cancellation.
paymentRead-only payment state. The API never changes payment; booking fees are handled in Flowyer.
Fields of payment
status"none" "awaiting" "partial" "paid" "refunded" method"card" "transfer" amountcurrencyinvoice_numberconfirmed_atcancelled_atcreated_atupdated_atPass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.
created_via_apiPresent when an external agent created the booking through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserat
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/bookings/4321' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/bookings/4321", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/bookings/4321",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 4321,
"booking_number": "string",
"status": "pending",
"source": "flowyer",
"lawyer": {
"user_id": 1234,
"name": "string"
},
"date": "2026-10-30",
"start_time": "string",
"end_time": "string",
"start": "2026-10-30T09:15:00+01:00",
"end": "2026-10-30T09:15:00+01:00",
"timezone": "string",
"location_preference": "office",
"meeting_link": "string",
"client": {
"name": "string",
"email": "info@example.hu",
"phone": "string",
"description": "string"
},
"gdpr_consent": true,
"gdpr_consent_at": "2026-10-30T09:15:00+01:00",
"cancel_reason": "string",
"payment": {
"status": "none",
"method": "card",
"amount": 0,
"currency": "string",
"invoice_number": "string"
},
"confirmed_at": "2026-10-30T09:15:00+01:00",
"cancelled_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"record_type": "booking",
"record_id": 4321
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Create booking
/api/agent/bookings
Books an appointment and EMAILS THE CLIENT the confirmation. Requires a free slot for the lawyer (else 409); the office calendar never blocks. source is "manual". An Idempotency-Key replay neither re-books nor re-emails.
Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
user_idrequiredLawyer the appointment is with (user_id from colleagues.list; must be an active office colleague).
42daterequiredAppointment day (YYYY-MM-DD) in the booking time zone.
"2026-03-02"timerequiredStart time HH:MM (24h) in the booking time zone. The end time is time + the office slot length. Must be a free slot.
^([01]\d|2[0-3]):[0-5]\d$"09:00"client_namerequiredThe client's name. Plain text; "<" and ">" are not allowed.
^[^<>]*$"Kovács Anna"client_emailrequiredThe client's e-mail: the recipient of the confirmation.
"anna@example.com"client_phoneThe client's phone. Plain text.
^[^<>]*$client_descriptionSubject / notes from the client. Plain text.
^[^<>]*$location_preferenceIn-office or online consultation. Default "office".
"office" "online" "office"meeting_linkOnline-meeting URL (http/https), for an online consultation.
gdpr_consentRecord the client's data-handling consent. Default true (office staff books on the client's behalf).
trueauto_confirmWhen true (or the office has auto-confirm on), create the booking already confirmed: run the calendar sync and send the client a confirmation instead of a request. Otherwise it is pending.
falseidempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
id4321booking_numberHuman booking number, e.g. "#B-0001".
statuspending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.
"pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired" sourceWhere the booking came from. An API-created booking is "manual".
"flowyer" "website" "embed_widget" "manual" lawyerThe lawyer (user_id from colleagues.list) the appointment is with.
Fields of lawyer
user_idnamedateCalendar date in the booking time zone (YYYY-MM-DD), stored literally.
start_timeStart time HH:MM in the booking time zone.
end_timeEnd time HH:MM in the booking time zone.
startUnambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.
endUnambiguous ISO 8601 instant of the end.
timezoneThe booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.
location_preferenceWhether the consultation is in the office or online.
"office" "online" meeting_linkOnline-meeting URL, when set.
clientThe external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.
Fields of client
nameemailphonedescriptiongdpr_consentWhether the client's data-handling consent is recorded.
gdpr_consent_atWhen the consent was recorded.
cancel_reasonReason recorded on cancellation.
paymentRead-only payment state. The API never changes payment; booking fees are handled in Flowyer.
Fields of payment
status"none" "awaiting" "partial" "paid" "refunded" method"card" "transfer" amountcurrencyinvoice_numberconfirmed_atcancelled_atcreated_atupdated_atPass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.
created_via_apiPresent when an external agent created the booking through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
id4321booking_numberHuman booking number, e.g. "#B-0001".
statuspending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.
"pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired" sourceWhere the booking came from. An API-created booking is "manual".
"flowyer" "website" "embed_widget" "manual" lawyerThe lawyer (user_id from colleagues.list) the appointment is with.
Fields of lawyer
user_idnamedateCalendar date in the booking time zone (YYYY-MM-DD), stored literally.
start_timeStart time HH:MM in the booking time zone.
end_timeEnd time HH:MM in the booking time zone.
startUnambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.
endUnambiguous ISO 8601 instant of the end.
timezoneThe booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.
location_preferenceWhether the consultation is in the office or online.
"office" "online" meeting_linkOnline-meeting URL, when set.
clientThe external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.
Fields of client
nameemailphonedescriptiongdpr_consentWhether the client's data-handling consent is recorded.
gdpr_consent_atWhen the consent was recorded.
cancel_reasonReason recorded on cancellation.
paymentRead-only payment state. The API never changes payment; booking fees are handled in Flowyer.
Fields of payment
status"none" "awaiting" "partial" "paid" "refunded" method"card" "transfer" amountcurrencyinvoice_numberconfirmed_atcancelled_atcreated_atupdated_atPass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.
created_via_apiPresent when an external agent created the booking through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/bookings' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"user_id": 42,
"date": "2026-03-02",
"time": "09:00",
"client_name": "Kovács Anna",
"client_email": "anna@example.com"
}'
const response = await fetch("https://www.flowyer.com/api/agent/bookings", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"user_id": 42,
"date": "2026-03-02",
"time": "09:00",
"client_name": "Kovács Anna",
"client_email": "anna@example.com"
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/bookings",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"user_id": 42,
"date": "2026-03-02",
"time": "09:00",
"client_name": "Kovács Anna",
"client_email": "anna@example.com",
},
timeout=30,
)
print(response.status_code, response.json())
{
"user_id": 42,
"date": "2026-03-02",
"time": "09:00",
"client_name": "Kovács Anna",
"client_email": "anna@example.com"
}
Response samples
{
"data": {
"id": 4321,
"booking_number": "string",
"status": "pending",
"source": "flowyer",
"lawyer": {
"user_id": 1234,
"name": "string"
},
"date": "2026-10-30",
"start_time": "string",
"end_time": "string",
"start": "2026-10-30T09:15:00+01:00",
"end": "2026-10-30T09:15:00+01:00",
"timezone": "string",
"location_preference": "office",
"meeting_link": "string",
"client": {
"name": "string",
"email": "info@example.hu",
"phone": "string",
"description": "string"
},
"gdpr_consent": true,
"gdpr_consent_at": "2026-10-30T09:15:00+01:00",
"cancel_reason": "string",
"payment": {
"status": "none",
"method": "card",
"amount": 0,
"currency": "string",
"invoice_number": "string"
},
"confirmed_at": "2026-10-30T09:15:00+01:00",
"cancelled_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": true
}
{
"data": {
"id": 4321,
"booking_number": "string",
"status": "pending",
"source": "flowyer",
"lawyer": {
"user_id": 1234,
"name": "string"
},
"date": "2026-10-30",
"start_time": "string",
"end_time": "string",
"start": "2026-10-30T09:15:00+01:00",
"end": "2026-10-30T09:15:00+01:00",
"timezone": "string",
"location_preference": "office",
"meeting_link": "string",
"client": {
"name": "string",
"email": "info@example.hu",
"phone": "string",
"description": "string"
},
"gdpr_consent": true,
"gdpr_consent_at": "2026-10-30T09:15:00+01:00",
"cancel_reason": "string",
"payment": {
"status": "none",
"method": "card",
"amount": 0,
"currency": "string",
"invoice_number": "string"
},
"confirmed_at": "2026-10-30T09:15:00+01:00",
"cancelled_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"user_id": [
"The user id field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Update booking
/api/agent/bookings/{id}
Reschedules (date + time together, re-checked for a free slot) or edits a booking (phone, notes, meeting link, location). Writes only the fields you send. Notifies the lawyer of a reschedule, never the client. Pass expected_updated_at from your last read; a stale write answers 409.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
dateNew day (YYYY-MM-DD) in the booking time zone. Send date AND time together to reschedule.
"2026-03-05"timeNew start time HH:MM. The end time is recomputed from the office slot length. Send date AND time together.
^([01]\d|2[0-3]):[0-5]\d$"11:00"client_phoneThe client's phone. Plain text. "" clears it.
^[^<>]*$client_descriptionSubject / notes. Plain text. "" clears it.
^[^<>]*$meeting_linkOnline-meeting URL (http/https). "" clears it.
location_preferenceIn-office or online consultation.
"office" "online" Responses
200
Updated.
datarequiredFields of data
id4321booking_numberHuman booking number, e.g. "#B-0001".
statuspending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.
"pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired" sourceWhere the booking came from. An API-created booking is "manual".
"flowyer" "website" "embed_widget" "manual" lawyerThe lawyer (user_id from colleagues.list) the appointment is with.
Fields of lawyer
user_idnamedateCalendar date in the booking time zone (YYYY-MM-DD), stored literally.
start_timeStart time HH:MM in the booking time zone.
end_timeEnd time HH:MM in the booking time zone.
startUnambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.
endUnambiguous ISO 8601 instant of the end.
timezoneThe booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.
location_preferenceWhether the consultation is in the office or online.
"office" "online" meeting_linkOnline-meeting URL, when set.
clientThe external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.
Fields of client
nameemailphonedescriptiongdpr_consentWhether the client's data-handling consent is recorded.
gdpr_consent_atWhen the consent was recorded.
cancel_reasonReason recorded on cancellation.
paymentRead-only payment state. The API never changes payment; booking fees are handled in Flowyer.
Fields of payment
status"none" "awaiting" "partial" "paid" "refunded" method"card" "transfer" amountcurrencyinvoice_numberconfirmed_atcancelled_atcreated_atupdated_atPass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.
created_via_apiPresent when an external agent created the booking through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratupdatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/bookings/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-03-05",
"time": "11:00"
}'
const response = await fetch("https://www.flowyer.com/api/agent/bookings/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-03-05",
"time": "11:00"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/bookings/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-03-05",
"time": "11:00",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"date": "2026-03-05",
"time": "11:00"
}
Response samples
{
"data": {
"id": 4321,
"booking_number": "string",
"status": "pending",
"source": "flowyer",
"lawyer": {
"user_id": 1234,
"name": "string"
},
"date": "2026-10-30",
"start_time": "string",
"end_time": "string",
"start": "2026-10-30T09:15:00+01:00",
"end": "2026-10-30T09:15:00+01:00",
"timezone": "string",
"location_preference": "office",
"meeting_link": "string",
"client": {
"name": "string",
"email": "info@example.hu",
"phone": "string",
"description": "string"
},
"gdpr_consent": true,
"gdpr_consent_at": "2026-10-30T09:15:00+01:00",
"cancel_reason": "string",
"payment": {
"status": "none",
"method": "card",
"amount": 0,
"currency": "string",
"invoice_number": "string"
},
"confirmed_at": "2026-10-30T09:15:00+01:00",
"cancelled_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Confirm booking
/api/agent/bookings/{id}/confirm
Confirms a pending booking and EMAILS THE CLIENT the confirmation (with the meeting link for an online one). Only a pending booking can be confirmed; any other status answers 409, so a retry never re-emails. Optionally pass expected_updated_at as a lock token.
Path parameters
idrequiredBooking id.
4321Request body application/json
expected_updated_atOptional optimistic-lock token: the updated_at from your last read. If the booking changed since, the API answers 409 and you re-read before retrying.
meeting_linkOnline-meeting URL (http/https) to set on the booking before the confirmation e-mail goes out.
Responses
200
OK.
datarequiredFields of data
id4321booking_numberHuman booking number, e.g. "#B-0001".
statuspending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.
"pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired" sourceWhere the booking came from. An API-created booking is "manual".
"flowyer" "website" "embed_widget" "manual" lawyerThe lawyer (user_id from colleagues.list) the appointment is with.
Fields of lawyer
user_idnamedateCalendar date in the booking time zone (YYYY-MM-DD), stored literally.
start_timeStart time HH:MM in the booking time zone.
end_timeEnd time HH:MM in the booking time zone.
startUnambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.
endUnambiguous ISO 8601 instant of the end.
timezoneThe booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.
location_preferenceWhether the consultation is in the office or online.
"office" "online" meeting_linkOnline-meeting URL, when set.
clientThe external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.
Fields of client
nameemailphonedescriptiongdpr_consentWhether the client's data-handling consent is recorded.
gdpr_consent_atWhen the consent was recorded.
cancel_reasonReason recorded on cancellation.
paymentRead-only payment state. The API never changes payment; booking fees are handled in Flowyer.
Fields of payment
status"none" "awaiting" "partial" "paid" "refunded" method"card" "transfer" amountcurrencyinvoice_numberconfirmed_atcancelled_atcreated_atupdated_atPass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.
created_via_apiPresent when an external agent created the booking through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserat
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/bookings/4321/confirm' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00"
}'
const response = await fetch("https://www.flowyer.com/api/agent/bookings/4321/confirm", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00"
}),
});
const result = await response.json();
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/bookings/4321/confirm",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00"
}
Response samples
{
"data": {
"id": 4321,
"booking_number": "string",
"status": "pending",
"source": "flowyer",
"lawyer": {
"user_id": 1234,
"name": "string"
},
"date": "2026-10-30",
"start_time": "string",
"end_time": "string",
"start": "2026-10-30T09:15:00+01:00",
"end": "2026-10-30T09:15:00+01:00",
"timezone": "string",
"location_preference": "office",
"meeting_link": "string",
"client": {
"name": "string",
"email": "info@example.hu",
"phone": "string",
"description": "string"
},
"gdpr_consent": true,
"gdpr_consent_at": "2026-10-30T09:15:00+01:00",
"cancel_reason": "string",
"payment": {
"status": "none",
"method": "card",
"amount": 0,
"currency": "string",
"invoice_number": "string"
},
"confirmed_at": "2026-10-30T09:15:00+01:00",
"cancelled_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Cancel booking
/api/agent/bookings/{id}/cancel
Cancels a booking and EMAILS THE CLIENT (and the lawyer) the cancellation. A booking already cancelled or completed answers 409, so a retry never re-emails. A paid booking is cancelled but never refunded (the office handles that). Optionally pass expected_updated_at.
Path parameters
idrequiredBooking id.
4321Request body application/json
expected_updated_atOptional optimistic-lock token: the updated_at from your last read. If the booking changed since, the API answers 409.
reasonReason recorded as cancel_reason and shown in the cancellation e-mail. Plain text; "<" and ">" are not allowed.
^[^<>]*$Responses
200
OK.
datarequiredFields of data
id4321booking_numberHuman booking number, e.g. "#B-0001".
statuspending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.
"pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired" sourceWhere the booking came from. An API-created booking is "manual".
"flowyer" "website" "embed_widget" "manual" lawyerThe lawyer (user_id from colleagues.list) the appointment is with.
Fields of lawyer
user_idnamedateCalendar date in the booking time zone (YYYY-MM-DD), stored literally.
start_timeStart time HH:MM in the booking time zone.
end_timeEnd time HH:MM in the booking time zone.
startUnambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.
endUnambiguous ISO 8601 instant of the end.
timezoneThe booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.
location_preferenceWhether the consultation is in the office or online.
"office" "online" meeting_linkOnline-meeting URL, when set.
clientThe external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.
Fields of client
nameemailphonedescriptiongdpr_consentWhether the client's data-handling consent is recorded.
gdpr_consent_atWhen the consent was recorded.
cancel_reasonReason recorded on cancellation.
paymentRead-only payment state. The API never changes payment; booking fees are handled in Flowyer.
Fields of payment
status"none" "awaiting" "partial" "paid" "refunded" method"card" "transfer" amountcurrencyinvoice_numberconfirmed_atcancelled_atcreated_atupdated_atPass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.
created_via_apiPresent when an external agent created the booking through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduserat
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), API disabled for the office, licence expired or office archived.
scope_missing), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/bookings/4321/cancel' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00"
}'
const response = await fetch("https://www.flowyer.com/api/agent/bookings/4321/cancel", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00"
}),
});
const result = await response.json();
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/bookings/4321/cancel",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00"
}
Response samples
{
"data": {
"id": 4321,
"booking_number": "string",
"status": "pending",
"source": "flowyer",
"lawyer": {
"user_id": 1234,
"name": "string"
},
"date": "2026-10-30",
"start_time": "string",
"end_time": "string",
"start": "2026-10-30T09:15:00+01:00",
"end": "2026-10-30T09:15:00+01:00",
"timezone": "string",
"location_preference": "office",
"meeting_link": "string",
"client": {
"name": "string",
"email": "info@example.hu",
"phone": "string",
"description": "string"
},
"gdpr_consent": true,
"gdpr_consent_at": "2026-10-30T09:15:00+01:00",
"cancel_reason": "string",
"payment": {
"status": "none",
"method": "card",
"amount": 0,
"currency": "string",
"invoice_number": "string"
},
"confirmed_at": "2026-10-30T09:15:00+01:00",
"cancelled_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Tickets
List tickets
/api/agent/tickets
Lists the support tickets the user may see (per-mailbox visibility), newest activity first. Filter by status (default open_states = new+open+pending; "all" includes closed), mailbox, assignee, requester email, or free text over subject/requester/ticket number.
Query parameters
statusopen_states (default) = new+open+pending (the web default view); "all" includes closed; or one exact status.
"new" "open" "pending" "closed" "open_states" "all" "open_states"mailbox_idOnly tickets from this mailbox. A mailbox the user may not see yields an empty list (never a leak).
7assigned_to_user_idOnly tickets assigned to this colleague (id from colleagues.list); 0 = only unassigned tickets. Omit for any assignee.
requester_emailOnly tickets whose external requester email matches exactly (case-insensitive).
"ugyfel@example.com"qFree-text search over subject, requester name/email and ticket number (case ignored).
"számla"limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idTicket id (use with tickets.get / tickets.update).
4321ticket_numberHuman ticket number, e.g. FLW-000123.
subjectstatus"new" "open" "pending" "closed" priority"low" "normal" "high" mailboxThe mailbox the ticket arrived through (central registry).
Fields of mailbox
idemaildisplay_namerequesterThe external person who wrote in.
Fields of requester
emailnameassigned_toThe colleague the ticket is assigned to, or null when unassigned.
Fields of assigned_to
user_idnamematterThe matter linked to the ticket, or null.
Fields of matter
idcase_numberclientThe client linked to the ticket, or null.
Fields of client
idnamelast_message_atTime of the most recent thread activity; the list is ordered by this, newest first.
created_atupdated_atPass this to tickets.update as expected_updated_at.
created_via_apiPresent when the ticket was created through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/tickets?mailbox_id=7&requester_email=ugyfel%40example.com&q=sz%C3%A1mla' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/tickets?mailbox_id=7&requester_email=ugyfel%40example.com&q=sz%C3%A1mla", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/tickets?mailbox_id=7&requester_email=ugyfel%40example.com&q=sz%C3%A1mla",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 4321,
"ticket_number": "string",
"subject": "string",
"status": "new",
"priority": "low",
"mailbox": {
"id": 1234,
"email": "info@example.hu",
"display_name": "string"
},
"requester": {
"email": "info@example.hu",
"name": "string"
},
"assigned_to": {
"user_id": 1234,
"name": "string"
},
"matter": {
"id": 1234,
"case_number": "string"
},
"client": {
"id": 1234,
"name": "string"
},
"last_message_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"tickets:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"tickets:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "tickets:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Get ticket
/api/agent/tickets/{id}
Returns one support ticket with its message thread (oldest first) and the linked matter/client. Answers 404 when the ticket does not exist or is in a mailbox the user may not see. The read never changes the ticket (a NEW ticket is not flipped to OPEN).
Path parameters
idrequiredTicket id.
4321Query parameters
messages_limitReturn only the newest N messages of the thread (still presented oldest first). Omit for the whole thread.
Responses
200
OK.
datarequiredFields of data
idTicket id (use with tickets.get / tickets.update).
4321ticket_numberHuman ticket number, e.g. FLW-000123.
subjectstatus"new" "open" "pending" "closed" priority"low" "normal" "high" mailboxThe mailbox the ticket arrived through (central registry).
Fields of mailbox
idemaildisplay_namerequesterThe external person who wrote in.
Fields of requester
emailnameassigned_toThe colleague the ticket is assigned to, or null when unassigned.
Fields of assigned_to
user_idnamematterThe matter linked to the ticket, or null.
Fields of matter
idcase_numberclientThe client linked to the ticket, or null.
Fields of client
idnamelast_message_atTime of the most recent thread activity; the list is ordered by this, newest first.
created_atupdated_atPass this to tickets.update as expected_updated_at.
created_via_apiPresent when the ticket was created through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratmessagesThe message thread, oldest first.
Fields of messages
iddirectionin = from the requester, out = reply/auto-ack sent from the mailbox, note = internal.
"in" "out" "note" fromFields of from
emailnametoFields of to
emailnameccFields of cc
emailnamesent_statusDelivery state of an outbound message; null for in/note.
"pending" "sent" "failed" "scheduled" body_textMessage body as plain text. The HTML body is deliberately omitted to keep the thread compact; when the stored text is empty it is rendered down from the HTML.
has_attachmentsauthorThe colleague who wrote an out/note message; null for an incoming message.
Fields of author
user_idnamecreated_atlinked_matterThe matter linked to the ticket (same as matter).
Fields of linked_matter
idcase_numberlinked_clientThe client linked to the ticket (same as client).
Fields of linked_client
idname
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/tickets/4321' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/tickets/4321", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/tickets/4321",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": {
"id": 4321,
"ticket_number": "string",
"subject": "string",
"status": "new",
"priority": "low",
"mailbox": {
"id": 1234,
"email": "info@example.hu",
"display_name": "string"
},
"requester": {
"email": "info@example.hu",
"name": "string"
},
"assigned_to": {
"user_id": 1234,
"name": "string"
},
"matter": {
"id": 1234,
"case_number": "string"
},
"client": {
"id": 1234,
"name": "string"
},
"last_message_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
},
"messages": [
{
"id": 1234,
"direction": "in",
"from": {
"email": "info@example.hu",
"name": "string"
},
"to": [
{
"email": "info@example.hu",
"name": "string"
}
],
"cc": [
{
"email": "info@example.hu",
"name": "string"
}
],
"sent_status": "pending",
"body_text": "string",
"has_attachments": true,
"author": {
"user_id": 1234,
"name": "string"
},
"created_at": "2026-10-30T09:15:00+01:00"
}
],
"linked_matter": {
"id": 1234,
"case_number": "string"
},
"linked_client": {
"id": 1234,
"name": "string"
}
}
}
{
"error": "not_found",
"message": "The record was not found, or it is not visible to the user who owns this API key.",
"message_localized": "The record was not found, or it is not visible to the user who owns this API key.",
"locale": "en",
"details": {
"record_type": "ticket",
"record_id": 4321
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Reply to ticket
/api/agent/tickets/{id}/reply
Replies to a support ticket and SENDS the reply by email to the external requester (through the ticket's mailbox). The body is rich HTML, sanitised server-side before store. A retry with the same Idempotency-Key never sends a second email.
Path parameters
idrequiredTicket id (must be visible via the mailbox ACL).
4321Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
body_htmlrequiredThe reply, rich HTML (as TinyMCE produces). Server-side HTMLPurifier-cleaned before store and before sending. At most 100000 characters; rejected if it sanitises to nothing.
"<p>Köszönjük megkeresését, csatoltan küldjük a választ.</p>"ccExtra Cc recipients (at most 10). The requester and the mailbox are excluded automatically; an invalid address answers 422.
statusTicket status to set after replying (default "pending" = waiting for the requester). Records a status-change event.
"new" "open" "pending" "closed" "pending"append_signatureAppend the key user's saved ticket signature to the OUTGOING mail only (never stored in the thread).
falseidempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
ticket_id4321message_idId of the created outbound ticket_messages row.
98765directionAlways "out".
sent_status"pending" at create time; the async send flips it to sent/failed later.
"pending" "sent" "failed" "scheduled" statusThe ticket status after the reply.
"new" "open" "pending" "closed" created_atcreated_via_apiPresent when the reply was created through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
ticket_id4321message_idId of the created outbound ticket_messages row.
98765directionAlways "out".
sent_status"pending" at create time; the async send flips it to sent/failed later.
"pending" "sent" "failed" "scheduled" statusThe ticket status after the reply.
"new" "open" "pending" "closed" created_atcreated_via_apiPresent when the reply was created through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/tickets/4321/reply' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"body_html": "<p>Köszönjük megkeresését, csatoltan küldjük a választ.</p>"
}'
const response = await fetch("https://www.flowyer.com/api/agent/tickets/4321/reply", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"body_html": "<p>Köszönjük megkeresését, csatoltan küldjük a választ.</p>"
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/tickets/4321/reply",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"body_html": "<p>Köszönjük megkeresését, csatoltan küldjük a választ.</p>",
},
timeout=30,
)
print(response.status_code, response.json())
{
"body_html": "<p>Köszönjük megkeresését, csatoltan küldjük a választ.</p>"
}
Response samples
{
"data": {
"ticket_id": 4321,
"message_id": 98765,
"direction": "string",
"sent_status": "pending",
"status": "new",
"created_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": true
}
{
"data": {
"ticket_id": 4321,
"message_id": 98765,
"direction": "string",
"sent_status": "pending",
"status": "new",
"created_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"body_html": [
"The body html field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Change ticket status
/api/agent/tickets/{id}
Changes a ticket's status (new, open, pending or closed). "closed" stamps closed_at; any other status clears it. Pass expected_updated_at from your last read; a conflicting change answers 409. Answers 404 when the ticket is not visible.
Path parameters
idrequiredId of the record to modify.
Request body application/json
expected_updated_atThe updated_at value from your most recent read of this record (required whenever the record has one). If the record changed since (a colleague edited it), the API answers 409 and you must re-read before retrying. A legacy record whose updated_at is null takes null.
statusrequiredThe new status. "closed" stamps closed_at; any other status clears it.
"new" "open" "pending" "closed" "open"Responses
200
Updated.
datarequiredFields of data
idTicket id (use with tickets.get / tickets.update).
4321ticket_numberHuman ticket number, e.g. FLW-000123.
subjectstatus"new" "open" "pending" "closed" priority"low" "normal" "high" mailboxThe mailbox the ticket arrived through (central registry).
Fields of mailbox
idemaildisplay_namerequesterThe external person who wrote in.
Fields of requester
emailnameassigned_toThe colleague the ticket is assigned to, or null when unassigned.
Fields of assigned_to
user_idnamematterThe matter linked to the ticket, or null.
Fields of matter
idcase_numberclientThe client linked to the ticket, or null.
Fields of client
idnamelast_message_atTime of the most recent thread activity; the list is ordered by this, newest first.
created_atupdated_atPass this to tickets.update as expected_updated_at.
created_via_apiPresent when the ticket was created through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratupdatedtrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The record changed since you read it (record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
record_conflict: expected_updated_at no longer matches; read it again and retry with the new updated_at).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X PATCH 'https://www.flowyer.com/api/agent/tickets/1234' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-d '{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"status": "open"
}'
const response = await fetch("https://www.flowyer.com/api/agent/tickets/1234", {
method: "PATCH",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"status": "open"
}),
});
const result = await response.json();
import requests
response = requests.patch(
"https://www.flowyer.com/api/agent/tickets/1234",
headers={
"Authorization": "Bearer flw_...",
},
json={
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"status": "open",
},
timeout=30,
)
print(response.status_code, response.json())
{
"expected_updated_at": "2026-10-30T09:15:00+01:00",
"status": "open"
}
Response samples
{
"data": {
"id": 4321,
"ticket_number": "string",
"subject": "string",
"status": "new",
"priority": "low",
"mailbox": {
"id": 1234,
"email": "info@example.hu",
"display_name": "string"
},
"requester": {
"email": "info@example.hu",
"name": "string"
},
"assigned_to": {
"user_id": 1234,
"name": "string"
},
"matter": {
"id": 1234,
"case_number": "string"
},
"client": {
"id": 1234,
"name": "string"
},
"last_message_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00",
"updated_at": "2026-10-30T09:15:00+01:00",
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
},
"updated": true
}
{
"error": "record_conflict",
"message": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"message_localized": "The record changed since you read it (someone edited it). Read it again and retry with the new updated_at.",
"locale": "en",
"details": {
"current_updated_at": "2026-10-30T10:42:05+01:00",
"reason": "modified_since_read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Portal
List client portals
/api/agent/portals
Lists the client portals (threads) the user may access: the matter, the client-side participants, the message and participant counts and the last message time, newest first. Optionally filtered to one matter (matter_id). A matter has at most one portal.
Query parameters
matter_idOnly the portal of this matter (404 matter_not_visible when the matter is not visible). A matter has at most one portal.
1234limitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idPortal (thread) id - the handle for portal.messages.list and portal.reply.
57matterThe matter (case) the portal belongs to.
Fields of matter
idcase_numberclient_participantsThe external client-side participants the firm is talking to.
Fields of client_participants
namekindclient = a principal, contact = a client contact, email = a legacy e-mail-only grant.
"client" "contact" "email" participant_countNumber of live participants (staff and client).
message_countNumber of non-deleted messages in the thread.
last_message_atWhen the newest non-deleted message was posted, ISO 8601 in the office zone; null for an empty thread.
unread_for_meUnread messages for the acting user's own participant row; null when the acting user is not a participant of this portal.
created_atpaginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/portals?matter_id=1234&limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/portals?matter_id=1234&limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/portals?matter_id=1234&limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 57,
"matter": {
"id": 1234,
"case_number": "string"
},
"client_participants": [
{
"name": "string",
"kind": "client"
}
],
"participant_count": 0,
"message_count": 0,
"last_message_at": "2026-10-30T09:15:00+01:00",
"unread_for_me": 0,
"created_at": "2026-10-30T09:15:00+01:00"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "scope_missing",
"message": "This API key does not have the \"portal:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"message_localized": "This API key does not have the \"portal:read\" permission. The permissions of an existing key cannot be changed, so the key owner needs to create a new key with this permission in Flowyer under Settings > API keys and use it in place of the old one.",
"locale": "en",
"details": {
"required_scope": "portal:read"
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
List portal messages
/api/agent/portals/{id}/messages
Lists the messages of a client portal thread, oldest first, with the author (staff or client), the plain-text body and any attachment metadata. Answers 404 when the portal does not exist or is not accessible. Does not mark messages as read.
Path parameters
idrequiredPortal id (must be accessible).
57Query parameters
include_deletedInclude deleted messages (their body is blanked). Default false.
falselimitPage size, 1-100. Default 25.
2525offsetNumber of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.
00Responses
200
OK.
datarequiredFields of data
idportal_messages.id.
8012authorWho wrote the message.
Fields of author
participant_idnamekindstaff = a colleague of the firm, client = the external client.
"staff" "client" directionfrom_firm = a staff author, from_client = the external client.
"from_firm" "from_client" bodyThe message as plain text (<br> decoded to line breaks). Empty string for a deleted message.
attachmentAttachment metadata when the message has a document; downloading it is out of scope. Null when there is none.
Fields of attachment
portal_document_idfile_nameis_deleteddeleted_atcreated_atWhen the message was posted, ISO 8601 in the office zone.
paginationrequiredFields of pagination
limitrequiredoffsetrequiredtotalrequiredTotal number of matching records.
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl 'https://www.flowyer.com/api/agent/portals/57/messages?limit=25' \
-H 'Authorization: Bearer flw_...'
const response = await fetch("https://www.flowyer.com/api/agent/portals/57/messages?limit=25", {
headers: {
"Authorization": "Bearer flw_...",
},
});
const result = await response.json();
import requests
response = requests.get(
"https://www.flowyer.com/api/agent/portals/57/messages?limit=25",
headers={
"Authorization": "Bearer flw_...",
},
timeout=30,
)
print(response.status_code, response.json())
Response samples
{
"data": [
{
"id": 8012,
"author": {
"participant_id": 1234,
"name": "string",
"kind": "staff"
},
"direction": "from_firm",
"body": "string",
"attachment": {
"portal_document_id": 1234,
"file_name": "string"
},
"is_deleted": true,
"deleted_at": "2026-10-30T09:15:00+01:00",
"created_at": "2026-10-30T09:15:00+01:00"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}
{
"error": "portal_not_visible",
"message": "The client portal :id does not exist, or is not accessible to the user who owns this API key.",
"message_localized": "The client portal :id does not exist, or is not accessible to the user who owns this API key.",
"locale": "en",
"details": {
"portal_id": 57
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}
Reply in client portal
/api/agent/portals/{id}/messages
Posts a staff reply in a client portal thread. The client sees it in the portal and is notified by e-mail (a signed magic-link notice, not the text); colleagues get an in-app notice. Plain text, escaped on store. 404 when the portal is not accessible.
Path parameters
idrequiredPortal id to reply in (must be accessible).
57Header parameters
Idempotency-KeyRecommended on every create. A unique value per logical operation (e.g. a UUID). The same value returns the same record: retrying with it returns the record created the first time instead of creating a second one. Different values always create different records. Clients that cannot send headers may pass the same value in the idempotency_key body field. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$Request body application/json
bodyrequiredThe reply, plain text. Angle brackets are accepted and escaped on store. At most 20000 characters.
"Tisztelt Ügyfelünk! A beadványt ma benyújtottuk."idempotency_keySame role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.
^[A-Za-z0-9_\-:.]{1,128}$"b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"Responses
201
Created.
datarequiredFields of data
messageThe posted reply.
Fields of message
id8012authorThe acting staff user who posted the reply.
Fields of author
participant_idnamekindstaff = a colleague of the firm, client = the external client.
"staff" "client" directionAlways "from_firm" for a reply posted through this API.
"from_firm" "from_client" bodyThe posted message as plain text.
created_atportalThe portal (thread) and its matter the reply was posted in.
Fields of portal
idmatterFields of matter
idcase_numbercreated_via_apiProvenance of the message created through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedtrue
200
Idempotent replay: this Idempotency-Key (or, without a key, an identical request within 120 seconds) already created the record; it is returned again, nothing new was created.
datarequiredFields of data
messageThe posted reply.
Fields of message
id8012authorThe acting staff user who posted the reply.
Fields of author
participant_idnamekindstaff = a colleague of the firm, client = the external client.
"staff" "client" directionAlways "from_firm" for a reply posted through this API.
"from_firm" "from_client" bodyThe posted message as plain text.
created_atportalThe portal (thread) and its matter the reply was posted in.
Fields of portal
idmatterFields of matter
idcase_numbercreated_via_apiProvenance of the message created through this API.
Fields of created_via_api
kind"key": created with a static API key; "oauth": created by an app connected through OAuth.
"key" "oauth" key_idId of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.
key_nameName the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".
user_iduseratcreatedfalseidempotent_replaytrue
400
Malformed request: invalid JSON body, wrong host (use the www. host), or an unreadable request.
401
The key is missing, invalid, revoked or expired, or its user is no longer active.
403
Missing scope (scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
scope_missing), missing Flowyer module permission (module_forbidden), API disabled for the office, licence expired or office archived.
404
Unknown endpoint, or the record does not exist / is not visible to the user. replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
replay_not_visible: this Idempotency-Key (or, without a key, an identical request) already created the record (details.record_id), but it has since been deleted or is not visible to the user; nothing new was created, do not create it again.
405
Method not offered on this path (DELETE and PUT never are; PATCH only where an update is offered).
409
The Idempotency-Key was used for another resource type (idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
idempotency_key_reused), or, without a key, an identical request is being processed right now (duplicate_in_progress; retry shortly).
413
The request body is too large (request_too_large).
request_too_large).
422
Validation failed or unknown field; details.fields lists the field errors.
details.fields lists the field errors.
429
Rate limit exceeded; Retry-After says when to retry.
Retry-After says when to retry.
503
The office is temporarily unavailable (maintenance or setup in progress); Retry-After says when to retry.
Retry-After says when to retry.
default
Any other error, in the same envelope.
Request samples
curl -X POST 'https://www.flowyer.com/api/agent/portals/57/messages' \
-H 'Authorization: Bearer flw_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f3a9c21-1b2c-4d5e-8f90-0a1b2c3d4e5f' \
-d '{
"body": "Tisztelt Ügyfelünk! A beadványt ma benyújtottuk."
}'
const response = await fetch("https://www.flowyer.com/api/agent/portals/57/messages", {
method: "POST",
headers: {
"Authorization": "Bearer flw_...",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"body": "Tisztelt Ügyfelünk! A beadványt ma benyújtottuk."
}),
});
const result = await response.json();
import uuid
import requests
response = requests.post(
"https://www.flowyer.com/api/agent/portals/57/messages",
headers={
"Authorization": "Bearer flw_...",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"body": "Tisztelt Ügyfelünk! A beadványt ma benyújtottuk.",
},
timeout=30,
)
print(response.status_code, response.json())
{
"body": "Tisztelt Ügyfelünk! A beadványt ma benyújtottuk."
}
Response samples
{
"data": {
"message": {
"id": 8012,
"author": {
"participant_id": 1234,
"name": "string",
"kind": "staff"
},
"direction": "from_firm",
"body": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"portal": {
"id": 1234,
"matter": {
"id": 1234,
"case_number": "string"
}
},
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
},
"created": true
}
{
"data": {
"message": {
"id": 8012,
"author": {
"participant_id": 1234,
"name": "string",
"kind": "staff"
},
"direction": "from_firm",
"body": "string",
"created_at": "2026-10-30T09:15:00+01:00",
"portal": {
"id": 1234,
"matter": {
"id": 1234,
"case_number": "string"
}
},
"created_via_api": {
"kind": "key",
"key_id": 1234,
"key_name": "string",
"user_id": 1234,
"user": "string",
"at": "2026-10-30T09:15:00+01:00"
}
}
},
"created": false,
"idempotent_replay": true
}
{
"error": "validation_failed",
"message": "The request is invalid. See \"details.fields\" for the field-level errors.",
"message_localized": "The request is invalid. See \"details.fields\" for the field-level errors.",
"locale": "en",
"details": {
"fields": {
"body": [
"The body field is required."
]
}
},
"request_id": "3f0c8a5e-2b1d-4c7a-9e6f-5a4b3c2d1e0f"
}