Flowyer API

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.

ScopeMeaningEndpoints
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.

HTTPerrorMeaning
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

GET /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.

datarequired
object
Fields of data
user
object
Fields of user
id
integer
name
string | null
language
string | null
office
object
Fields of office
id
integer
name
string | null
timezone
string

IANA zone the API reads and writes timestamps in, e.g. Europe/Budapest.

currency_id
integer

The office currency (Flowyer currency id): costs.create books in it when currency_id is omitted.

currency
string | null

ISO code of the office currency, e.g. HUF, EUR, RON.

key
object
Fields of key
id
integer
name
string

Name the user gave this key, or the connected app for an OAuth token.

kind
string

"key" = a static API key, "oauth" = an OAuth 2.1 access token.

Allowed values: "key" "oauth"
prefix
string | null

First characters of a static key, for identification only; null for OAuth.

expires_at
string <date-time> | null
last_used_at
string <date-time> | null
scopes
string[]

Scopes of this credential, e.g. "matters:read". An operation whose scope is missing answers 403.

available_scopes
string[]

All scopes the key owner could enable in Flowyer.

limits
object
Fields of limits
requests_per_minute
integer
list_max_limit
integer
channel
string

How the request reached Flowyer.

Allowed values: "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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
  }
}

Colleagues

List colleagues

GET /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.

Scope
colleagues:read

Query parameters

q
stringat most 255 characters

A colleague's name to look up, as heard or dictated, in any word order. Omit it to list every active colleague.

Example: "Nagy Péter"
limit
integer>= 1<= 500

Maximum colleagues to return (default 200).

Default: 200

Responses

200

OK.

datarequired
object
Fields of data
status
string | null

Only 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.

Allowed values: "exact" "similar" "none"
colleagues
object[]

Active colleagues, best match first with q, otherwise by name.

Fields of colleagues
id
integer

User id: pass it as assigned_to_user_id / responsible_user_id.

name
string

Display name as Flowyer shows it (with the title, if any).

title
string | null

Name prefix such as "Dr.", or null.

total
integer

How many colleagues matched (all active colleagues without q) before the list was cut to limit.

truncated
boolean

True 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

Response samples

{
  "data": {
    "status": "exact",
    "colleagues": [
      {
        "id": 1234,
        "name": "string",
        "title": "string"
      }
    ],
    "total": 1,
    "truncated": true
  }
}

Matters

List matters

GET /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.

Scope
matters:read
Flowyer permission
cases:read

Query parameters

q
stringat most 100 characters

Free-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".

Example: "2026/0025"
client_id
integer>= 1

Only matters of this client.

Example: 512
responsible_user_id
integer>= 1

Only 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_since
string <date-time>

Only matters changed at or after this time (ISO 8601).

Example: "2026-10-01T00:00:00+02:00"
include_archived
boolean

Include archived matters.

Default: false
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer

Matter id. Use it as matter_id elsewhere.

Example: 1234
case_number
string

Case number as displayed in Flowyer, in the office numbering scheme.

Example: "2026/0025"
subject
string
Example: "Kovács Kft. v. Nagy Bt. - contract dispute"
status
string
Allowed values: "active" "archived"
case_type_id
integer | null
case_type
string | null

Localized name of the case type.

responsible_user_id
integer | null
responsible
string | null

Display name of the responsible lawyer.

clients
object[]

Clients (principals) of the matter.

Fields of clients
id
integer
name
string
started_on
string <date> | null
restricted
boolean

True when only the responsible lawyers may see the matter (Chinese wall).

created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Resolve matter by name

GET /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.

Scope
matters:read
Flowyer permission
cases:read

Query parameters

namerequired
stringat least 1 charactersat most 255 characters

Matter subject or client name to look up, as heard or dictated.

Example: "ingatlan adásvétel"
client_id
integer | null>= 1

Only matters of this client (use after clients.resolve returned an exact client).

limit
integer>= 1<= 10

Maximum candidates to return (default 5).

Default: 5

Responses

200

OK.

datarequired
object
Fields of data
status
string

exact = 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.

Allowed values: "exact" "similar" "none"
matters
object[]

Candidate matters, best match first.

Fields of matters
id
integer

Matter id. Use it as matter_id elsewhere.

Example: 1234
case_number
string

Case number as displayed in Flowyer, in the office numbering scheme.

Example: "2026/0025"
subject
string
Example: "Kovács Kft. v. Nagy Bt. - contract dispute"
status
string
Allowed values: "active" "archived"
case_type_id
integer | null
case_type
string | null

Localized name of the case type.

responsible_user_id
integer | null
responsible
string | null

Display name of the responsible lawyer.

clients
object[]

Clients (principals) of the matter.

Fields of clients
id
integer
name
string
started_on
string <date> | null
restricted
boolean

True when only the responsible lawyers may see the matter (Chinese wall).

created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
total
integer

How many matters matched before the list was cut to limit.

truncated
boolean

True 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get matter

GET /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.

Scope
matters:read
Flowyer permission
cases:read

Path parameters

idrequired
integer>= 1

Matter id.

Example: 1234

Responses

200

OK.

datarequired
object
Fields of data
id
integer

Matter id. Use it as matter_id elsewhere.

Example: 1234
case_number
string

Case number as displayed in Flowyer, in the office numbering scheme.

Example: "2026/0025"
subject
string
Example: "Kovács Kft. v. Nagy Bt. - contract dispute"
status
string
Allowed values: "active" "archived"
case_type_id
integer | null
case_type
string | null

Localized name of the case type.

responsible_user_id
integer | null
responsible
string | null

Display name of the responsible lawyer.

clients
object[]

Clients (principals) of the matter.

Fields of clients
id
integer
name
string
started_on
string <date> | null
restricted
boolean

True when only the responsible lawyers may see the matter (Chinese wall).

created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
description
string | null
litigious
boolean
archived_at
string <date> | null

Day 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
  }
}

Create matter

POST /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.

Scope
matters:create
Flowyer permission
cases:create

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

subjectrequired
stringat least 2 charactersat most 255 characters

Short title of the matter as it appears in lists. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
Example: "Kovács Kft. v. Nagy Bt. - contract dispute"
case_type_idrequired
integer>= 1

Case 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_idsrequired
integer[]at least 1 itemsat most 20 items

Ids of the clients (principals). At least one, at most 20.

Example: [ 512 ]
responsible_user_id
integer | null>= 1

Responsible lawyer: a user id from colleagues.list (look a dictated name up there). Default: the user behind the key.

started_on
string <date> | null

Start date, YYYY-MM-DD. Default: today.

Example: "2026-10-29"
description
string | nullat most 10000 characters

Longer description, plain text.

main_case_number
string | nullat most 100 characters

The office's own main reference number of the matter. Required when the office numbering scheme uses it (the API answers 422 otherwise).

restricted
boolean

true = only the responsible lawyers may see the matter (Chinese wall).

Default: false
idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer

Matter id. Use it as matter_id elsewhere.

Example: 1234
case_number
string

Case number as displayed in Flowyer, in the office numbering scheme.

Example: "2026/0025"
subject
string
Example: "Kovács Kft. v. Nagy Bt. - contract dispute"
status
string
Allowed values: "active" "archived"
case_type_id
integer | null
case_type
string | null

Localized name of the case type.

responsible_user_id
integer | null
responsible
string | null

Display name of the responsible lawyer.

clients
object[]

Clients (principals) of the matter.

Fields of clients
id
integer
name
string
started_on
string <date> | null
restricted
boolean

True when only the responsible lawyers may see the matter (Chinese wall).

created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
description
string | null
litigious
boolean
archived_at
string <date> | null

Day the matter was archived, YYYY-MM-DD. Flowyer records the date only, no time of day.

created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer

Matter id. Use it as matter_id elsewhere.

Example: 1234
case_number
string

Case number as displayed in Flowyer, in the office numbering scheme.

Example: "2026/0025"
subject
string
Example: "Kovács Kft. v. Nagy Bt. - contract dispute"
status
string
Allowed values: "active" "archived"
case_type_id
integer | null
case_type
string | null

Localized name of the case type.

responsible_user_id
integer | null
responsible
string | null

Display name of the responsible lawyer.

clients
object[]

Clients (principals) of the matter.

Fields of clients
id
integer
name
string
started_on
string <date> | null
restricted
boolean

True when only the responsible lawyers may see the matter (Chinese wall).

created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
description
string | null
litigious
boolean
archived_at
string <date> | null

Day the matter was archived, YYYY-MM-DD. Flowyer records the date only, no time of day.

created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Update matter

PATCH /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.

Scope
matters:update
Flowyer permission
cases:modify

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

subject
string | nullat least 1 characters

Short 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.

description
string | null

Longer description, plain text, at most 10000 characters as a NEW value; a longer stored description may be sent back unchanged.

started_on
string <date> | null

Start date, YYYY-MM-DD. null keeps the current value (a start date cannot be cleared).

litigious
boolean

Whether the matter is litigious.

responsible_user_id
integer | null>= 0

New 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.

datarequired
object
Fields of data
id
integer

Matter id. Use it as matter_id elsewhere.

Example: 1234
case_number
string

Case number as displayed in Flowyer, in the office numbering scheme.

Example: "2026/0025"
subject
string
Example: "Kovács Kft. v. Nagy Bt. - contract dispute"
status
string
Allowed values: "active" "archived"
case_type_id
integer | null
case_type
string | null

Localized name of the case type.

responsible_user_id
integer | null
responsible
string | null

Display name of the responsible lawyer.

clients
object[]

Clients (principals) of the matter.

Fields of clients
id
integer
name
string
started_on
string <date> | null
restricted
boolean

True when only the responsible lawyers may see the matter (Chinese wall).

created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
description
string | null
litigious
boolean
archived_at
string <date> | null

Day the matter was archived, YYYY-MM-DD. Flowyer records the date only, no time of day.

updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

List matter responsibles

GET /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.

Scope
matters:read
Flowyer permission
cases:read

Path parameters

idrequired
integer>= 1

Matter id.

Example: 1234

Query parameters

limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
type
string

"user" = a single colleague; "workgroup" = every member of a workgroup.

Allowed values: "user" "workgroup"
user_id
integer | null

Colleague user id for a "user" entry (pass it as user_id to matters.responsibles.add); null for a workgroup.

workgroup_id
integer | null

Workgroup id for a "workgroup" entry; null for a single colleague. Workgroup responsibles are managed in Flowyer.

name
string | null

Display name of the colleague or workgroup.

paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

Response samples

{
  "data": [
    {
      "type": "user",
      "user_id": 1234,
      "workgroup_id": 1234,
      "name": "string"
    }
  ],
  "pagination": {
    "limit": 25,
    "offset": 0,
    "total": 1
  }
}

Add matter responsible

POST /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.

Scope
matters:update
Flowyer permission
cases:modify

Path parameters

idrequired
integer>= 1

Matter the colleague becomes responsible for (must be visible).

Example: 1234

Request body application/json

user_idrequired
integer>= 1

Colleague to add: a user id from colleagues.list. Adding them gives them access to the matter.

Example: 42

Responses

200

OK.

datarequired
object
Fields of data
user_id
integer

The colleague now responsible for the matter.

name
string | null

Display name of the colleague.

already_responsible
boolean

True when the colleague was already a responsible of the matter (nothing changed).

notified
boolean

True 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.

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).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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
}'

Response samples

{
  "data": {
    "user_id": 1234,
    "name": "string",
    "already_responsible": true,
    "notified": true
  }
}

Clients

List clients

GET /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.

Scope
clients:read
Flowyer permission
contacts:read

Query parameters

q
stringat most 100 characters

Search in name (accents and case ignored), e-mail and tax number.

Example: "Kovács"
is_person
boolean

true = natural persons only, false = companies only.

include_archived
boolean
Default: false
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer

Client id. Use it as client_id elsewhere.

Example: 512
name
string
Example: "Kovács Kft."
is_person
boolean

True for a natural person, false for a company.

email
string | null
phone
string | null
archived
boolean
recorded_by_user_id
integer | null
recorded_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Resolve client by name

GET /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.

Scope
clients:read
Flowyer permission
contacts:read

Query parameters

namerequired
stringat least 1 charactersat most 255 characters

Client name to look up, as heard or dictated.

Example: "Józsi ingatlan"
limit
integer>= 1<= 10

Maximum candidates to return (default 5).

Default: 5

Responses

200

OK.

datarequired
object
Fields of data
status
string

exact = 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.

Allowed values: "exact" "similar" "none"
matches
object[]

Candidate clients, best match first.

Fields of matches
id
integer

Client id. Use it as client_id elsewhere.

Example: 512
name
string
Example: "Kovács Kft."
is_person
boolean

True for a natural person, false for a company.

email
string | null
phone
string | null
archived
boolean
recorded_by_user_id
integer | null
recorded_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
total
integer

How many clients matched before the list was cut to limit.

truncated
boolean

True 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get client

GET /api/agent/clients/{id}

Returns one client with address and registration details. Identity-document numbers and fee settings are never returned.

Scope
clients:read
Flowyer permission
contacts:read

Path parameters

idrequired
integer>= 1

Client id.

Example: 512

Responses

200

OK.

datarequired
object
Fields of data
id
integer

Client id. Use it as client_id elsewhere.

Example: 512
name
string
Example: "Kovács Kft."
is_person
boolean

True for a natural person, false for a company.

email
string | null
phone
string | null
archived
boolean
recorded_by_user_id
integer | null
recorded_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
address
object
Fields of address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
mailing_address
object
Fields of mailing_address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
tax_number
string | null

Companies only; null for a natural person.

company_registration_number
string | null

Companies only; null for a natural person.

representative_name
string | null

Companies only; null for a natural person.

representative_position
string | null

Companies only; null for a natural person.

main_activity
string | null

Main scope of activity. Companies only; null for a natural person.

nationality_country
string | null
note
string | null
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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
  }
}

Create client

POST /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.

Scope
clients:create
Flowyer permission
contacts:create

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

namerequired
stringat least 2 charactersat most 255 characters

Full name of the person or the company name. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
Example: "Kovács Kft."
is_person
boolean

true for a natural person, false (default) for a company.

Default: false
email
string | nullat most 190 characters
Example: "info@kovacskft.hu"
phone
string | nullat most 64 characters
Example: "+36 1 234 5678"
address
object | null

Registered / home address.

Fields of address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
mailing_address
object | null
Fields of mailing_address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
tax_number
string | nullat most 64 characters

Company tax number (companies only).

Example: "12345678-2-41"
company_registration_number
string | nullat most 64 characters

Company registration number (companies only).

Example: "01-09-123456"
representative_name
string | nullat most 255 characters

Companies only.

representative_position
string | nullat most 255 characters

Companies only.

main_activity
string | nullat most 255 characters

Main scope of activity (companies only).

nationality_country
string | nullat most 100 characters
note
string | nullat most 4000 characters
allow_duplicate_name
boolean

Create even if an active client with the same name exists.

Default: false
idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer

Client id. Use it as client_id elsewhere.

Example: 512
name
string
Example: "Kovács Kft."
is_person
boolean

True for a natural person, false for a company.

email
string | null
phone
string | null
archived
boolean
recorded_by_user_id
integer | null
recorded_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
address
object
Fields of address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
mailing_address
object
Fields of mailing_address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
tax_number
string | null

Companies only; null for a natural person.

company_registration_number
string | null

Companies only; null for a natural person.

representative_name
string | null

Companies only; null for a natural person.

representative_position
string | null

Companies only; null for a natural person.

main_activity
string | null

Main scope of activity. Companies only; null for a natural person.

nationality_country
string | null
note
string | null
created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer

Client id. Use it as client_id elsewhere.

Example: 512
name
string
Example: "Kovács Kft."
is_person
boolean

True for a natural person, false for a company.

email
string | null
phone
string | null
archived
boolean
recorded_by_user_id
integer | null
recorded_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
address
object
Fields of address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
mailing_address
object
Fields of mailing_address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
tax_number
string | null

Companies only; null for a natural person.

company_registration_number
string | null

Companies only; null for a natural person.

representative_name
string | null

Companies only; null for a natural person.

representative_position
string | null

Companies only; null for a natural person.

main_activity
string | null

Main scope of activity. Companies only; null for a natural person.

nationality_country
string | null
note
string | null
created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Update client

PATCH /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.

Scope
clients:update
Flowyer permission
contacts:modify

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

name
string | nullat least 1 characters

Full 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.

email
string | nullat most 190 characters
phone
string | nullat most 64 characters
address
object | null

Registered / home address.

Fields of address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
mailing_address
object | null
Fields of mailing_address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
representative_name
string | nullat most 255 characters

Companies only.

representative_position
string | nullat most 255 characters

Companies only.

main_activity
string | nullat most 255 characters

Main scope of activity (companies only).

nationality_country
string | nullat most 100 characters
note
string | null

Free-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.

datarequired
object
Fields of data
id
integer

Client id. Use it as client_id elsewhere.

Example: 512
name
string
Example: "Kovács Kft."
is_person
boolean

True for a natural person, false for a company.

email
string | null
phone
string | null
archived
boolean
recorded_by_user_id
integer | null
recorded_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
address
object
Fields of address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
mailing_address
object
Fields of mailing_address
zip
string | nullat most 20 characters
city
string | nullat most 100 characters
street
string | nullat most 255 characters
tax_number
string | null

Companies only; null for a natural person.

company_registration_number
string | null

Companies only; null for a natural person.

representative_name
string | null

Companies only; null for a natural person.

representative_position
string | null

Companies only; null for a natural person.

main_activity
string | null

Main scope of activity. Companies only; null for a natural person.

nationality_country
string | null
note
string | null
updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Tasks

List tasks

GET /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.

Scope
tasks:read
Flowyer permission
tasks:read

Query parameters

status
string

open (default), closed or all.

Allowed values: "open" "closed" "all"
Default: "open"
assigned_to_user_id
integer>= 1

Only tasks assigned to this user (a user id from colleagues.list).

matter_id
integer>= 1

Only tasks of this matter.

client_id
integer>= 1

Only caseless tasks of this client.

deadline_from
string <date>

Deadline on or after this date.

Example: "2026-11-01"
deadline_to
string <date>

Deadline on or before this date.

Example: "2026-11-30"
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer
Example: 9876
subject
string
Example: "Submit the statement of defence"
description
string | null
deadline
string <date> | null

Due date, YYYY-MM-DD.

Example: "2026-11-15"
priority
integer | null

1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).

Example: 2
status
string
Allowed values: "open" "closed"
closed_at
string <date> | null

Day the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.

matter
object | null

The matter the task belongs to, if any.

Fields of matter
id
integer
case_number
string
client
object | null

The client: the matter's first client, or the task's own client when it has no matter.

Fields of client
id
integer
name
string
assigned_to_user_id
integer | null
assigned_to
string | null
created_by_user_id
integer | null
created_by
string | null
flag_id
integer | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get task

GET /api/agent/tasks/{id}

Returns one task. Answers 404 when the task does not exist or the user may not see it.

Scope
tasks:read
Flowyer permission
tasks:read

Path parameters

idrequired
integer>= 1

Task id.

Example: 9876

Responses

200

OK.

datarequired
object
Fields of data
id
integer
Example: 9876
subject
string
Example: "Submit the statement of defence"
description
string | null
deadline
string <date> | null

Due date, YYYY-MM-DD.

Example: "2026-11-15"
priority
integer | null

1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).

Example: 2
status
string
Allowed values: "open" "closed"
closed_at
string <date> | null

Day the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.

matter
object | null

The matter the task belongs to, if any.

Fields of matter
id
integer
case_number
string
client
object | null

The client: the matter's first client, or the task's own client when it has no matter.

Fields of client
id
integer
name
string
assigned_to_user_id
integer | null
assigned_to
string | null
created_by_user_id
integer | null
created_by
string | null
flag_id
integer | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
    }
  }
}

Create task

POST /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.

Scope
tasks:create
Flowyer permission
tasks:create

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

subjectrequired
stringat least 2 charactersat most 1000 characters

What has to be done. Plain text.

Example: "Submit the statement of defence"
deadlinerequired
string <date>

Due date, YYYY-MM-DD.

Example: "2026-11-15"
matter_id
integer | null>= 1

Matter the task belongs to. Omit for a caseless task.

Example: 1234
client_id
integer | null>= 1

Client of a caseless task. Not allowed together with matter_id (a matter's task takes the matter's client).

assigned_to_user_id
integer | null>= 1

Colleague responsible for the task: a user id from colleagues.list (look a dictated name up there). Default: the user behind the key.

priority
integer>= 1<= 3

1 = low, 2 = medium (default), 3 = high.

Default: 2
description
string | nullat most 10000 characters

Longer details, plain text.

flag_id
integer | null>= 1

Id of an office task flag (label).

idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer
Example: 9876
subject
string
Example: "Submit the statement of defence"
description
string | null
deadline
string <date> | null

Due date, YYYY-MM-DD.

Example: "2026-11-15"
priority
integer | null

1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).

Example: 2
status
string
Allowed values: "open" "closed"
closed_at
string <date> | null

Day the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.

matter
object | null

The matter the task belongs to, if any.

Fields of matter
id
integer
case_number
string
client
object | null

The client: the matter's first client, or the task's own client when it has no matter.

Fields of client
id
integer
name
string
assigned_to_user_id
integer | null
assigned_to
string | null
created_by_user_id
integer | null
created_by
string | null
flag_id
integer | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer
Example: 9876
subject
string
Example: "Submit the statement of defence"
description
string | null
deadline
string <date> | null

Due date, YYYY-MM-DD.

Example: "2026-11-15"
priority
integer | null

1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).

Example: 2
status
string
Allowed values: "open" "closed"
closed_at
string <date> | null

Day the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.

matter
object | null

The matter the task belongs to, if any.

Fields of matter
id
integer
case_number
string
client
object | null

The client: the matter's first client, or the task's own client when it has no matter.

Fields of client
id
integer
name
string
assigned_to_user_id
integer | null
assigned_to
string | null
created_by_user_id
integer | null
created_by
string | null
flag_id
integer | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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
}'

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
}

Update task

PATCH /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.

Scope
tasks:update
Flowyer permission
tasks:modify

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

subject
string | nullat least 1 characters

What 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.

deadline
string <date> | null

Due date, YYYY-MM-DD. null keeps the current deadline (a deadline cannot be cleared).

matter_id
integer | null>= 1

Move 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_id
integer | null>= 1

Client of a caseless task. Only allowed when the task has no matter.

assigned_to_user_id
integer | null>= 0

New 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.

priority
integer>= 1<= 3

1 = low, 2 = medium, 3 = high. Omit it to keep the current priority (also when the task reads back with priority null); null is refused.

description
string | null

Longer details, plain text, at most 10000 characters as a NEW value; a longer stored description may be sent back unchanged.

flag_id
integer | null>= 1

Id of an office task flag (label), or null to clear it.

Responses

200

Updated.

datarequired
object
Fields of data
id
integer
Example: 9876
subject
string
Example: "Submit the statement of defence"
description
string | null
deadline
string <date> | null

Due date, YYYY-MM-DD.

Example: "2026-11-15"
priority
integer | null

1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).

Example: 2
status
string
Allowed values: "open" "closed"
closed_at
string <date> | null

Day the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.

matter
object | null

The matter the task belongs to, if any.

Fields of matter
id
integer
case_number
string
client
object | null

The client: the matter's first client, or the task's own client when it has no matter.

Fields of client
id
integer
name
string
assigned_to_user_id
integer | null
assigned_to
string | null
created_by_user_id
integer | null
created_by
string | null
flag_id
integer | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Close task

POST /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.

Scope
tasks:update
Flowyer permission
tasks:modify

Path parameters

idrequired
integer>= 1

Task id.

Example: 9876

Request body application/json

minutes_spent
integer | null>= 0<= 100000

Time spent, in minutes. Booking any time needs the workhours:create scope and a case.

minutes_invoiceable
integer | null>= 0<= 100000

Billable minutes; clipped to the office hour-cap.

minutes_normative
integer | null>= 0<= 100000

Normative minutes; stored raw, never capped.

matter_id
integer | null>= 1

Assign 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_id
integer | null>= 1

Direct client (Megbízó) for a caseless task closed without a case.

Responses

200

OK.

datarequired
object
Fields of data
id
integer
Example: 9876
subject
string
Example: "Submit the statement of defence"
description
string | null
deadline
string <date> | null

Due date, YYYY-MM-DD.

Example: "2026-11-15"
priority
integer | null

1 = low, 2 = medium, 3 = high; null = not set (an older task without a priority).

Example: 2
status
string
Allowed values: "open" "closed"
closed_at
string <date> | null

Day the task was closed, YYYY-MM-DD. Flowyer records the date only, no time of day.

matter
object | null

The matter the task belongs to, if any.

Fields of matter
id
integer
case_number
string
client
object | null

The client: the matter's first client, or the task's own client when it has no matter.

Fields of client
id
integer
name
string
assigned_to_user_id
integer | null
assigned_to
string | null
created_by_user_id
integer | null
created_by
string | null
flag_id
integer | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created_via_api
object | null
Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
booked_workhour
object | null

The time entry booked from the close, or null when no time was entered.

Fields of booked_workhour
id
integer>= 1
minutes
integer
minutes_invoiceable
integer
minutes_normative
integer
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.

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).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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 '{}'

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
    }
  }
}

Workhours

List time entries

GET /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.

Scope
workhours:read
Flowyer permission
workhours:read

Query parameters

matter_id
integer>= 1

Only entries on this matter.

Example: 1234
responsible_user_id
integer>= 1

Only entries booked to this colleague (a user id from colleagues.list).

date_from
string <date>

Only entries on or after this date (YYYY-MM-DD).

date_to
string <date>

Only entries on or before this date (YYYY-MM-DD).

limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer

Time entry id.

matter_id
integer | null

Matter the entry is booked to.

date
string <date> | null
activity
string | null

What was done (plain text).

minutes
integer | null

Minutes spent.

minutes_invoiceable
integer | null

Billable minutes after the office hour-cap was applied (may be lower than requested). They count towards billing only while excluded is false.

minutes_normative
integer | null

Normative minutes (informational; never capped).

responsible_user_id
integer | null

Colleague the time is booked to.

responsible
string | null
recorded_by_user_id
integer | null

Who recorded the entry.

locked
boolean

True when the entry is locked (read-only through the API).

excluded
boolean

True 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_at
string <date-time> | null
updated_at
string <date-time> | null

Send this back as expected_updated_at when updating.

paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get time entry

GET /api/agent/workhours/{id}

Returns one live time entry by id, if its matter is visible to the user.

Scope
workhours:read
Flowyer permission
workhours:read

Path parameters

idrequired
integer>= 1

Time entry id.

Responses

200

OK.

datarequired
object
Fields of data
id
integer

Time entry id.

matter_id
integer | null

Matter the entry is booked to.

date
string <date> | null
activity
string | null

What was done (plain text).

minutes
integer | null

Minutes spent.

minutes_invoiceable
integer | null

Billable minutes after the office hour-cap was applied (may be lower than requested). They count towards billing only while excluded is false.

minutes_normative
integer | null

Normative minutes (informational; never capped).

responsible_user_id
integer | null

Colleague the time is booked to.

responsible
string | null
recorded_by_user_id
integer | null

Who recorded the entry.

locked
boolean

True when the entry is locked (read-only through the API).

excluded
boolean

True 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_at
string <date-time> | null
updated_at
string <date-time> | null

Send 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
  }
}

Record time entry

POST /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.

Scope
workhours:create
Flowyer permission
workhours:create

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

matter_idrequired
integer>= 1

Matter the time is booked to (must be visible).

Example: 1234
daterequired
string <date>

Date of the work, YYYY-MM-DD.

Example: "2026-10-30"
subject
stringat least 2 charactersat most 1000 characters

What 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.

Pattern: ^[^<>]*$
Example: "Drafting the statement of claim"
activity
stringat least 2 charactersat most 1000 characters

The same field as subject, under the name the read side uses. Send one of the two.

Pattern: ^[^<>]*$
minutes
integer | null>= 0<= 100000

Minutes actually spent.

Example: 90
minutes_invoiceable
integer | null>= 0<= 100000

Billable 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.

Example: 60
minutes_normative
integer | null>= 0<= 100000

Normative minutes (informational; never capped).

responsible_user_id
integer | null>= 1

Colleague the time is booked to: a user id from colleagues.list (look a dictated name up there). Default: the user behind the key.

idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer

Time entry id.

matter_id
integer | null

Matter the entry is booked to.

date
string <date> | null
activity
string | null

What was done (plain text).

minutes
integer | null

Minutes spent.

minutes_invoiceable
integer | null

Billable minutes after the office hour-cap was applied (may be lower than requested). They count towards billing only while excluded is false.

minutes_normative
integer | null

Normative minutes (informational; never capped).

responsible_user_id
integer | null

Colleague the time is booked to.

responsible
string | null
recorded_by_user_id
integer | null

Who recorded the entry.

locked
boolean

True when the entry is locked (read-only through the API).

excluded
boolean

True 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_at
string <date-time> | null
updated_at
string <date-time> | null

Send this back as expected_updated_at when updating.

created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer

Time entry id.

matter_id
integer | null

Matter the entry is booked to.

date
string <date> | null
activity
string | null

What was done (plain text).

minutes
integer | null

Minutes spent.

minutes_invoiceable
integer | null

Billable minutes after the office hour-cap was applied (may be lower than requested). They count towards billing only while excluded is false.

minutes_normative
integer | null

Normative minutes (informational; never capped).

responsible_user_id
integer | null

Colleague the time is booked to.

responsible
string | null
recorded_by_user_id
integer | null

Who recorded the entry.

locked
boolean

True when the entry is locked (read-only through the API).

excluded
boolean

True 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_at
string <date-time> | null
updated_at
string <date-time> | null

Send this back as expected_updated_at when updating.

created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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
}'

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
}

Update time entry

PATCH /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.

Scope
workhours:update
Flowyer permission
workhours:modify

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

date
string <date> | null

Date of the work, YYYY-MM-DD. null keeps the current date.

subject
string | nullat least 1 characters

What 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.

activity
string | nullat least 1 characters

The same field as subject, under the name the read side uses. Send one of the two (null under the other one is fine).

minutes
integer | null>= 0<= 100000

Minutes actually spent; null clears them.

minutes_invoiceable
integer | null>= 0<= 100000

Billable 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_normative
integer | null>= 0<= 100000

Normative minutes (never capped); null clears them.

responsible_user_id
integer | null>= 0

New 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.

datarequired
object
Fields of data
id
integer

Time entry id.

matter_id
integer | null

Matter the entry is booked to.

date
string <date> | null
activity
string | null

What was done (plain text).

minutes
integer | null

Minutes spent.

minutes_invoiceable
integer | null

Billable minutes after the office hour-cap was applied (may be lower than requested). They count towards billing only while excluded is false.

minutes_normative
integer | null

Normative minutes (informational; never capped).

responsible_user_id
integer | null

Colleague the time is booked to.

responsible
string | null
recorded_by_user_id
integer | null

Who recorded the entry.

locked
boolean

True when the entry is locked (read-only through the API).

excluded
boolean

True 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_at
string <date-time> | null
updated_at
string <date-time> | null

Send this back as expected_updated_at when updating.

updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Costs

List costs

GET /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.

Scope
costs:read
Flowyer permission
cases:read, summary:read

Query parameters

matter_id
integer>= 1

Only costs on this matter.

Example: 1234
responsible_user_id
integer>= 1

Only costs recorded for this colleague (a user id from colleagues.list).

date_from
string <date>

Only costs on or after this date (YYYY-MM-DD).

date_to
string <date>

Only costs on or before this date (YYYY-MM-DD).

limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer

Cost id.

matter_id
integer | null

Matter the cost belongs to.

date
string <date> | null
activity
string | null

Description of the cost (plain text).

cost
number | null

Net cost amount (no VAT).

cost_invoiceable
number | null

Net amount to bill on to the client. It counts towards billing only while excluded is false.

currency_id
integer | null

Currency id (Flowyer currency list).

currency
string | null

Currency ISO code, e.g. HUF, EUR.

responsible_user_id
integer | null
responsible
string | null
excluded
boolean

True when a colleague excluded the cost from the matter summary: it is not billed and is left out of the billable totals.

locked
boolean

True 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_billing
boolean

True 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_at
string <date-time> | null
updated_at
string <date-time> | null

Send this back as expected_updated_at when updating.

paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get cost

GET /api/agent/costs/{id}

Returns one live cost by id, if its matter is visible to the user. Amounts are net.

Scope
costs:read
Flowyer permission
cases:read, summary:read

Path parameters

idrequired
integer>= 1

Cost id.

Responses

200

OK.

datarequired
object
Fields of data
id
integer

Cost id.

matter_id
integer | null

Matter the cost belongs to.

date
string <date> | null
activity
string | null

Description of the cost (plain text).

cost
number | null

Net cost amount (no VAT).

cost_invoiceable
number | null

Net amount to bill on to the client. It counts towards billing only while excluded is false.

currency_id
integer | null

Currency id (Flowyer currency list).

currency
string | null

Currency ISO code, e.g. HUF, EUR.

responsible_user_id
integer | null
responsible
string | null
excluded
boolean

True when a colleague excluded the cost from the matter summary: it is not billed and is left out of the billable totals.

locked
boolean

True 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_billing
boolean

True 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_at
string <date-time> | null
updated_at
string <date-time> | null

Send 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
  }
}

Record cost

POST /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.

Scope
costs:create
Flowyer permission
cases:create, summary:read

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

matter_idrequired
integer>= 1

Matter the cost belongs to (must be visible).

Example: 1234
daterequired
string <date>

Date of the cost, YYYY-MM-DD.

Example: "2026-10-30"
subject
stringat least 2 charactersat most 1000 characters

Description 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.

Pattern: ^[^<>]*$
Example: "Court filing fee"
activity
stringat least 2 charactersat most 1000 characters

The same field as subject, under the name the read side uses. Send one of the two.

Pattern: ^[^<>]*$
costrequired
integer>= 0<= 2147483647

Net cost amount (no VAT), a whole number in the currency's main unit (cents are not stored).

Example: 15000
cost_invoiceable
integer | null>= 0<= 2147483647

Net 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.

Example: 15000
currency_id
integer | null>= 1

Currency 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_id
integer | null>= 1

Responsible colleague: a user id from colleagues.list (look a dictated name up there). Default: the user behind the key.

idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer

Cost id.

matter_id
integer | null

Matter the cost belongs to.

date
string <date> | null
activity
string | null

Description of the cost (plain text).

cost
number | null

Net cost amount (no VAT).

cost_invoiceable
number | null

Net amount to bill on to the client. It counts towards billing only while excluded is false.

currency_id
integer | null

Currency id (Flowyer currency list).

currency
string | null

Currency ISO code, e.g. HUF, EUR.

responsible_user_id
integer | null
responsible
string | null
excluded
boolean

True when a colleague excluded the cost from the matter summary: it is not billed and is left out of the billable totals.

locked
boolean

True 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_billing
boolean

True 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_at
string <date-time> | null
updated_at
string <date-time> | null

Send this back as expected_updated_at when updating.

created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer

Cost id.

matter_id
integer | null

Matter the cost belongs to.

date
string <date> | null
activity
string | null

Description of the cost (plain text).

cost
number | null

Net cost amount (no VAT).

cost_invoiceable
number | null

Net amount to bill on to the client. It counts towards billing only while excluded is false.

currency_id
integer | null

Currency id (Flowyer currency list).

currency
string | null

Currency ISO code, e.g. HUF, EUR.

responsible_user_id
integer | null
responsible
string | null
excluded
boolean

True when a colleague excluded the cost from the matter summary: it is not billed and is left out of the billable totals.

locked
boolean

True 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_billing
boolean

True 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_at
string <date-time> | null
updated_at
string <date-time> | null

Send this back as expected_updated_at when updating.

created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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
}'

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
}

Update cost

PATCH /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.

Scope
costs:update
Flowyer permission
cases:modify, summary:read

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

date
string <date> | null

Date of the cost, YYYY-MM-DD. null keeps the current date.

subject
string | nullat least 1 characters

Description 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.

activity
string | nullat least 1 characters

The same field as subject, under the name the read side uses. Send one of the two (null under the other one is fine).

cost
integer | null>= 0<= 2147483647

Net cost amount (no VAT), a whole number in the currency's main unit (cents are not stored). null clears the amount.

cost_invoiceable
integer | null>= 0<= 2147483647

Net 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_id
integer | null>= 0

Currency 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_id
integer | null>= 0

New 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.

datarequired
object
Fields of data
id
integer

Cost id.

matter_id
integer | null

Matter the cost belongs to.

date
string <date> | null
activity
string | null

Description of the cost (plain text).

cost
number | null

Net cost amount (no VAT).

cost_invoiceable
number | null

Net amount to bill on to the client. It counts towards billing only while excluded is false.

currency_id
integer | null

Currency id (Flowyer currency list).

currency
string | null

Currency ISO code, e.g. HUF, EUR.

responsible_user_id
integer | null
responsible
string | null
excluded
boolean

True when a colleague excluded the cost from the matter summary: it is not billed and is left out of the billable totals.

locked
boolean

True 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_billing
boolean

True 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_at
string <date-time> | null
updated_at
string <date-time> | null

Send this back as expected_updated_at when updating.

updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Invoices

List invoices

GET /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.

Scope
invoices:read
Flowyer permission
invoices:read, summary:read

Query parameters

direction
string

The 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.

Allowed values: "incoming" "outgoing" "proforma"
paid_status
string
Allowed values: "unpaid" "paid" "partially_paid" "cancelled"
client_id
integer>= 1
matter_id
integer>= 1
issued_from
string <date>

Issue date on or after.

Example: "2026-01-01"
issued_to
string <date>

Issue date on or before.

Example: "2026-12-31"
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer
Example: 301
invoice_number
string | null
Example: "FLW-2026-0042"
direction
string | null

outgoing = issued by the office, incoming = received by the office.

Allowed values: "incoming" "outgoing"
document_type
string | null

"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.

Allowed values: "invoice" "proforma" "receipt"
source
string | null
Allowed values: "manual" "szamlazz_outgoing" "szamlazz_imported"
paid_status
string | null
Allowed values: "unpaid" "paid" "partially_paid" "cancelled"
issued_date
string <date> | null
due_date
string <date> | null
paid_date
string <date> | null
completion_date
string <date> | null
net_amount
number | null
gross_amount
number | null
currency
string | null
Example: "HUF"
payment_method_id
integer | null
payment_method
string | null

Localized 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.

supplier
string | null

Issuer of an incoming invoice.

client
object | null
Fields of client
id
integer
name
string
matter_id
integer | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get invoice

GET /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.

Scope
invoices:read
Flowyer permission
invoices:read, summary:read

Path parameters

idrequired
integer>= 1

Invoice id.

Example: 301

Responses

200

OK.

datarequired
object
Fields of data
id
integer
Example: 301
invoice_number
string | null
Example: "FLW-2026-0042"
direction
string | null

outgoing = issued by the office, incoming = received by the office.

Allowed values: "incoming" "outgoing"
document_type
string | null

"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.

Allowed values: "invoice" "proforma" "receipt"
source
string | null
Allowed values: "manual" "szamlazz_outgoing" "szamlazz_imported"
paid_status
string | null
Allowed values: "unpaid" "paid" "partially_paid" "cancelled"
issued_date
string <date> | null
due_date
string <date> | null
paid_date
string <date> | null
completion_date
string <date> | null
net_amount
number | null
gross_amount
number | null
currency
string | null
Example: "HUF"
payment_method_id
integer | null
payment_method
string | null

Localized 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.

supplier
string | null

Issuer of an incoming invoice.

client
object | null
Fields of client
id
integer
name
string
matter_id
integer | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
notes
string | null
received_date
string <date> | null
sent_date
string <date> | null
proforma_invoice_id
integer | null
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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Documents

List matter documents

GET /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.

Scope
documents:read
Flowyer permission
cases:read

Path parameters

idrequired
integer>= 1

Matter id.

Example: 1234

Query parameters

q
stringat most 100 characters

Filter by file name.

Example: "contract"
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer
name
string

File 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.

Example: "Contract_v2.docx"
size
integer | null

Bytes.

version
integer

Current version number of the file.

folder
object | null

The 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
id
integer
name
string
path
string

Folder path inside the matter, "/" separated.

folder_deleted
boolean

True 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_id
integer | null
created_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

List matter folders

GET /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.

Scope
documents:read
Flowyer permission
cases:read

Path parameters

idrequired
integer>= 1

Matter id.

Example: 1234

Query parameters

q
stringat most 100 characters

Only folders whose path contains this text (case and accents ignored), e.g. a folder name.

Example: "Beadványok"
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer

Folder id: pass it as folder_id to documents.upload.init.

name
string

Folder name as shown in Flowyer.

parent_id
integer | null

Id of the parent folder; null for a folder at the matter root.

path
string

Folder path inside the matter, "/" separated, e.g. "/Iratok/Beadványok" (the same path documents.list shows for a file's folder).

updated_at
string <date-time> | null

Pass this to documents.folders.rename as expected_updated_at.

paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Create matter folder

POST /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.

Scope
documents:create
Flowyer permission
cases:create

Path parameters

idrequired
integer>= 1

Matter the folder belongs to (must be visible).

Example: 1234

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

namerequired
stringat least 1 charactersat most 255 characters

Folder name, at most 255 characters. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
Example: "Beadványok"
parent_id
integer | null>= 1

Parent folder id (from documents.folders) inside the SAME matter; omit for a folder at the matter root.

idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer

Folder id: pass it as folder_id to documents.upload.init, or as parent_id to documents.folders.create.

name
string

Folder name as shown in Flowyer.

parent_id
integer | null

Id of the parent folder; null for a folder at the matter root.

path
string

Folder path inside the matter, "/" separated, e.g. "/Iratok/Beadványok".

updated_at
string <date-time> | null

Pass this to documents.folders.rename as expected_updated_at.

created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer

Folder id: pass it as folder_id to documents.upload.init, or as parent_id to documents.folders.create.

name
string

Folder name as shown in Flowyer.

parent_id
integer | null

Id of the parent folder; null for a folder at the matter root.

path
string

Folder path inside the matter, "/" separated, e.g. "/Iratok/Beadványok".

updated_at
string <date-time> | null

Pass this to documents.folders.rename as expected_updated_at.

created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

Response samples

{
  "data": {
    "id": 1234,
    "name": "string",
    "parent_id": 1234,
    "path": "string",
    "updated_at": "2026-10-30T09:15:00+01:00"
  },
  "created": true
}

Start document upload

POST /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.

Scope
documents:create
Flowyer permission
cases:create

Path parameters

idrequired
integer>= 1

Matter the document belongs to (must be visible).

Request body application/json

filenamerequired
stringat least 1 charactersat most 255 characters

File name with extension, e.g. "Contract.pdf". Plain text; "<" and ">" are not allowed. Executable and web file types are refused (422 blocked_file_type).

Pattern: ^[^<>]*$
Example: "Contract.pdf"
content_typerequired
stringat least 1 charactersat most 100 characters

MIME type sent on the PUT, e.g. "application/pdf". It is part of the signature and must match exactly.

Example: "application/pdf"
sizerequired
integer>= 1

File size in bytes, at most 104857600 (the upload limit); a larger size answers 413 file_too_large.

Example: 248000
folder_id
integer | null>= 1

Folder inside the matter: a folder id from documents.folders (it lists empty folders too). Omit for the matter root.

Responses

200

OK.

datarequired
object
Fields of data
upload_url
string

Signed 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_token
string

Pass this to documents.upload.complete after the PUT succeeds.

content_type
string

Send exactly this Content-Type on the PUT.

if_generation_match
string

Send 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_key
string

Internal storage name (for reference).

expires_in
integer

Seconds 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.

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).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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
}'

Response samples

{
  "data": {
    "upload_url": "string",
    "upload_token": "string",
    "content_type": "string",
    "if_generation_match": "string",
    "object_key": "string",
    "expires_in": 0
  }
}

Finish document upload

POST /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.

Scope
documents:create
Flowyer permission
cases:create

Path parameters

idrequired
integer>= 1

Matter the document belongs to (same as in init).

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

upload_tokenrequired
stringat least 1 charactersat most 4096 characters

The token returned by documents.upload.init.

idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer
name
string

File 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.

Example: "Contract_v2.docx"
size
integer | null

Bytes.

version
integer

Current version number of the file.

folder
object | null

The 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
id
integer
name
string
path
string

Folder path inside the matter, "/" separated.

folder_deleted
boolean

True 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_id
integer | null
created_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer
name
string

File 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.

Example: "Contract_v2.docx"
size
integer | null

Bytes.

version
integer

Current version number of the file.

folder
object | null

The 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
id
integer
name
string
path
string

Folder path inside the matter, "/" separated.

folder_deleted
boolean

True 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_id
integer | null
created_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

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).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Download document

GET /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.

Scope
documents:download
Flowyer permission
cases:read

Path parameters

idrequired
integer>= 1

Document id (from documents.list).

Responses

200

OK.

datarequired
object
Fields of data
download_url
string

Signed URL. GET it to download the file bytes.

filename
string

File name for the download.

size
integer | null

Bytes.

expires_in
integer

Seconds 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

Response samples

{
  "data": {
    "download_url": "string",
    "filename": "string",
    "size": 0,
    "expires_in": 0
  }
}

Rename document

PATCH /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.

Scope
documents:update
Flowyer permission
cases:modify

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

namerequired
stringat least 1 charactersat most 255 characters

New 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.

Pattern: ^[^<>]*$
Example: "Contract_signed.pdf"

Responses

200

Updated.

datarequired
object
Fields of data
id
integer
name
string

File 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.

Example: "Contract_v2.docx"
size
integer | null

Bytes.

version
integer

Current version number of the file.

folder
object | null

The 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
id
integer
name
string
path
string

Folder path inside the matter, "/" separated.

folder_deleted
boolean

True 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_id
integer | null
created_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null
updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Rename matter folder

PATCH /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.

Scope
documents:update
Flowyer permission
cases:modify

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

namerequired
stringat least 1 charactersat most 255 characters

New folder name, at most 255 characters. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
Example: "Beadványok"

Responses

200

Updated.

datarequired
object
Fields of data
id
integer

Folder id: pass it as folder_id to documents.upload.init, or as parent_id to documents.folders.create.

name
string

Folder name as shown in Flowyer.

parent_id
integer | null

Id of the parent folder; null for a folder at the matter root.

path
string

Folder path inside the matter, "/" separated, e.g. "/Iratok/Beadványok".

updated_at
string <date-time> | null

Pass this to documents.folders.rename as expected_updated_at.

updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

Response samples

{
  "data": {
    "id": 1234,
    "name": "string",
    "parent_id": 1234,
    "path": "string",
    "updated_at": "2026-10-30T09:15:00+01:00"
  },
  "updated": true
}

Knowledge base

List knowledge base articles

GET /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.

Scope
knowledge:read

Query parameters

q
string | nullat most 255 characters

Full-text search term (titles, excerpts, content and tag names). Boolean operators are stripped.

Example: "felmondási idő"
status
string | null

Only articles with this status. Omit to list every status.

Allowed values: "draft" "published" "archived"
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer
Example: 9876
slug
string

Server-generated URL slug.

Example: "felmondasi-ido"
title
string
Example: "Felmondási idő a munka törvénykönyvében"
excerpt
string | null

Short summary (rich HTML, as stored). Use knowledge.articles.get for the full body.

status
string
Allowed values: "draft" "published" "archived"
is_featured
boolean

Whether the article is pinned to the KB home page (not settable through the API).

category_id
integer | null
category_name
string | null
author_id
integer

User id of the author; 0 for a global (default) article.

author_name
string | null
editor_id
integer | null

User id of the last editor, or null if never edited.

editor_name
string | null
views_count
integer
downloads_count
integer
shared_roles
integer[]

Role ids the article is shared with (empty = only the author/global).

tags
string[]

Tag names on the article.

published_at
string <date-time> | null

When the article was first published, or null.

created_at
string <date-time> | null
updated_at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get knowledge base article

GET /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.

Scope
knowledge:read

Path parameters

idrequired
integer>= 1

Article id.

Example: 9876

Responses

200

OK.

datarequired
object
Fields of data
id
integer
Example: 9876
slug
string

Server-generated URL slug.

Example: "felmondasi-ido"
title
string
Example: "Felmondási idő a munka törvénykönyvében"
excerpt
string | null

Short summary (rich HTML, as stored). Use knowledge.articles.get for the full body.

status
string
Allowed values: "draft" "published" "archived"
is_featured
boolean

Whether the article is pinned to the KB home page (not settable through the API).

category_id
integer | null
category_name
string | null
author_id
integer

User id of the author; 0 for a global (default) article.

author_name
string | null
editor_id
integer | null

User id of the last editor, or null if never edited.

editor_name
string | null
views_count
integer
downloads_count
integer
shared_roles
integer[]

Role ids the article is shared with (empty = only the author/global).

tags
string[]

Tag names on the article.

published_at
string <date-time> | null

When the article was first published, or null.

created_at
string <date-time> | null
updated_at
string <date-time> | null
content
string

The 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
  }
}

Create knowledge base article

POST /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.

Scope
knowledge:create

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

titlerequired
stringat most 500 characters

Article title. Plain text.

Example: "Felmondási idő a munka törvénykönyvében"
excerpt
string | null

Short summary (HTML allowed, sanitised server-side), at most 200 characters.

contentrequired
string

The article body. Rich HTML, sanitised server-side with the knowledge-base profile.

category_idrequired
integer>= 1

Id of an existing, active knowledge-base category.

Example: 12
statusrequired
string

draft, published or archived. published stamps published_at.

Allowed values: "draft" "published" "archived"
tags
string[] | null

Tag names; new tags are created as needed.

shared_roles
integer[] | null

Role ids to share the article with. Empty means only the author can see it.

idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer
Example: 9876
slug
string

Server-generated URL slug.

Example: "felmondasi-ido"
title
string
Example: "Felmondási idő a munka törvénykönyvében"
excerpt
string | null

Short summary (rich HTML, as stored). Use knowledge.articles.get for the full body.

status
string
Allowed values: "draft" "published" "archived"
is_featured
boolean

Whether the article is pinned to the KB home page (not settable through the API).

category_id
integer | null
category_name
string | null
author_id
integer

User id of the author; 0 for a global (default) article.

author_name
string | null
editor_id
integer | null

User id of the last editor, or null if never edited.

editor_name
string | null
views_count
integer
downloads_count
integer
shared_roles
integer[]

Role ids the article is shared with (empty = only the author/global).

tags
string[]

Tag names on the article.

published_at
string <date-time> | null

When the article was first published, or null.

created_at
string <date-time> | null
updated_at
string <date-time> | null
content
string

The full article body as stored (rich HTML).

created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer
Example: 9876
slug
string

Server-generated URL slug.

Example: "felmondasi-ido"
title
string
Example: "Felmondási idő a munka törvénykönyvében"
excerpt
string | null

Short summary (rich HTML, as stored). Use knowledge.articles.get for the full body.

status
string
Allowed values: "draft" "published" "archived"
is_featured
boolean

Whether the article is pinned to the KB home page (not settable through the API).

category_id
integer | null
category_name
string | null
author_id
integer

User id of the author; 0 for a global (default) article.

author_name
string | null
editor_id
integer | null

User id of the last editor, or null if never edited.

editor_name
string | null
views_count
integer
downloads_count
integer
shared_roles
integer[]

Role ids the article is shared with (empty = only the author/global).

tags
string[]

Tag names on the article.

published_at
string <date-time> | null

When the article was first published, or null.

created_at
string <date-time> | null
updated_at
string <date-time> | null
content
string

The full article body as stored (rich HTML).

created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Update knowledge base article

PATCH /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.

Scope
knowledge:update

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

title
string | null

Article title (plain text). null keeps the current title; a new title re-derives the slug.

excerpt
string | null

Short summary (HTML allowed, sanitised), at most 200 characters as a NEW value. Empty/null clears it; omit to keep it.

content
string | null

The article body (rich HTML, sanitised). null keeps the current content; it cannot be cleared.

category_id
integer | null>= 1

Move the article to this existing, active category. null keeps the current one.

status
string | null

draft, published or archived. The first switch to published stamps published_at; it is never cleared. null keeps the current status.

Allowed values: "draft" "published" "archived"
tags
string[] | null

Replaces the whole tag set (new tags are created). An empty array clears all tags; omit to keep them.

shared_roles
integer[] | null

Replaces the whole shared-role set. An empty array removes all sharing (only the author sees it); omit to keep it.

Responses

200

Updated.

datarequired
object
Fields of data
id
integer
Example: 9876
slug
string

Server-generated URL slug.

Example: "felmondasi-ido"
title
string
Example: "Felmondási idő a munka törvénykönyvében"
excerpt
string | null

Short summary (rich HTML, as stored). Use knowledge.articles.get for the full body.

status
string
Allowed values: "draft" "published" "archived"
is_featured
boolean

Whether the article is pinned to the KB home page (not settable through the API).

category_id
integer | null
category_name
string | null
author_id
integer

User id of the author; 0 for a global (default) article.

author_name
string | null
editor_id
integer | null

User id of the last editor, or null if never edited.

editor_name
string | null
views_count
integer
downloads_count
integer
shared_roles
integer[]

Role ids the article is shared with (empty = only the author/global).

tags
string[]

Tag names on the article.

published_at
string <date-time> | null

When the article was first published, or null.

created_at
string <date-time> | null
updated_at
string <date-time> | null
content
string

The full article body as stored (rich HTML).

updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

List knowledge base categories

GET /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).

Scope
knowledge:read

Query parameters

limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer
Example: 42
slug
string

URL slug, generated from the name.

Example: "munkajog"
name
string
Example: "Munkajog"
description
string | null

Plain-text description, or null.

icon
string | null

Icon identifier shown next to the category, or null.

order_column
integer

Sort position among sibling categories.

Example: 0
is_active
boolean
parent_id
integer | null

Parent category id, or null for a root category.

author_id
integer

User id of the creator; 0 for a built-in default category visible to everyone.

author_name
string | null

Display name of the creator, or null for a default category.

articles_count
integer

Number of published articles in this category that the caller may see.

shared_roles
integer[]

Role 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_at
string <date-time> | null
updated_at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get knowledge base category

GET /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.

Scope
knowledge:read

Path parameters

idrequired
integer>= 1

Category id.

Example: 42

Responses

200

OK.

datarequired
object
Fields of data
id
integer
Example: 42
slug
string

URL slug, generated from the name.

Example: "munkajog"
name
string
Example: "Munkajog"
description
string | null

Plain-text description, or null.

icon
string | null

Icon identifier shown next to the category, or null.

order_column
integer

Sort position among sibling categories.

Example: 0
is_active
boolean
parent_id
integer | null

Parent category id, or null for a root category.

author_id
integer

User id of the creator; 0 for a built-in default category visible to everyone.

author_name
string | null

Display name of the creator, or null for a default category.

articles_count
integer

Number of published articles in this category that the caller may see.

shared_roles
integer[]

Role 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_at
string <date-time> | null
updated_at
string <date-time> | null
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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
  }
}

Create knowledge base category

POST /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.

Scope
knowledge:create

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

namerequired
stringat most 255 characters

Category name. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
Example: "Munkajog"
description
string | null

Optional description, plain text.

parent_id
integer | null>= 1

Parent category id (must exist and be active). Omit for a root category.

icon
string | nullat most 100 characters

Optional icon identifier. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
is_active
boolean | null

Whether the category is active. Default true.

Default: true
order_column
integer | null>= 0

Sort position among sibling categories. Default 0.

Example: 0
shared_roles
integer[] | null

Role ids to share this category with. Omit so only you (the author) can see it.

idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer
Example: 42
slug
string

URL slug, generated from the name.

Example: "munkajog"
name
string
Example: "Munkajog"
description
string | null

Plain-text description, or null.

icon
string | null

Icon identifier shown next to the category, or null.

order_column
integer

Sort position among sibling categories.

Example: 0
is_active
boolean
parent_id
integer | null

Parent category id, or null for a root category.

author_id
integer

User id of the creator; 0 for a built-in default category visible to everyone.

author_name
string | null

Display name of the creator, or null for a default category.

articles_count
integer

Number of published articles in this category that the caller may see.

shared_roles
integer[]

Role 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_at
string <date-time> | null
updated_at
string <date-time> | null
created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer
Example: 42
slug
string

URL slug, generated from the name.

Example: "munkajog"
name
string
Example: "Munkajog"
description
string | null

Plain-text description, or null.

icon
string | null

Icon identifier shown next to the category, or null.

order_column
integer

Sort position among sibling categories.

Example: 0
is_active
boolean
parent_id
integer | null

Parent category id, or null for a root category.

author_id
integer

User id of the creator; 0 for a built-in default category visible to everyone.

author_name
string | null

Display name of the creator, or null for a default category.

articles_count
integer

Number of published articles in this category that the caller may see.

shared_roles
integer[]

Role 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_at
string <date-time> | null
updated_at
string <date-time> | null
created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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
}'

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
}

Update knowledge base category

PATCH /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.

Scope
knowledge:update

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

name
string | nullat least 1 characters

Category 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.

description
string | null

Plain-text description. null clears it.

parent_id
integer | null>= 1

Move the category under this parent (must exist, be active and not create a cycle), or null to make it a root category.

icon
string | null

Icon identifier. At most 100 characters as a NEW value; "<" and ">" cannot be introduced. null clears it.

is_active
boolean | null

Active flag. Omit or null to keep the current value.

order_column
integer | null>= 0

Sort position among sibling categories. Omit or null to keep the current value.

shared_roles
integer[] | null

Replace 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.

datarequired
object
Fields of data
id
integer
Example: 42
slug
string

URL slug, generated from the name.

Example: "munkajog"
name
string
Example: "Munkajog"
description
string | null

Plain-text description, or null.

icon
string | null

Icon identifier shown next to the category, or null.

order_column
integer

Sort position among sibling categories.

Example: 0
is_active
boolean
parent_id
integer | null

Parent category id, or null for a root category.

author_id
integer

User id of the creator; 0 for a built-in default category visible to everyone.

author_name
string | null

Display name of the creator, or null for a default category.

articles_count
integer

Number of published articles in this category that the caller may see.

shared_roles
integer[]

Role 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_at
string <date-time> | null
updated_at
string <date-time> | null
updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Emails

List matter emails

GET /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.

Scope
emails:read
Flowyer permission
emails:read

Path parameters

idrequired
integer>= 1

Matter id.

Example: 1234

Query parameters

q
stringat most 100 characters

Filter by subject, sender name or sender address (accent- and case-insensitive). Not the body.

Example: "invoice"
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer

Email id (use with emails.get).

Example: 1234
from_name
string | null

Display 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_address
string | null

SMTP address of the outside party.

subject
string | null
sent_at
string <date-time> | null

When 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.

size
integer | null

Bytes of the stored .eml/.msg file.

extension
string | null

"msg" (Outlook add-in) or "eml" (IMAP / default); null on some legacy rows.

saved_by_user_id
integer | null

User id of the colleague who saved the mail into Flowyer.

saved_by
string | null

Display name of that colleague.

created_at
string <date-time> | null

When the mail was saved into Flowyer (office-zone wall time). The reliable timestamp for stable pagination.

paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get email

GET /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.

Scope
emails:read
Flowyer permission
emails:read

Path parameters

idrequired
integer>= 1

Email id.

Example: 1234

Responses

200

OK.

datarequired
object
Fields of data
id
integer
Example: 1234
matter
object | null

The matter the mail is filed under, or null for a caseless mail (saved outside any case).

Fields of matter
id
integer
case_number
string
from_name
string | null

Display name of the outside party (never a colleague; the store has no direction column).

from_address
string | null

SMTP address of the outside party.

subject
string | null
body
string | null

The 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_at
string <date-time> | null

When 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.

size
integer | null

Bytes of the stored .eml/.msg file.

extension
string | null

"msg" or "eml"; null on some legacy rows.

saved_by_user_id
integer | null

User id of the colleague who saved the mail into Flowyer.

saved_by
string | null

Display name of that colleague.

created_at
string <date-time> | null

When the mail was saved into Flowyer (office-zone wall time).

updated_at
string <date-time> | null
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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
  }
}

Litigation

List matter litigation history

GET /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.

Scope
litigation:read
Flowyer permission
cases:read

Path parameters

idrequired
integer>= 1

Matter id.

Example: 1234

Query parameters

limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer
Example: 4521
title
string

Entry title. Plain text.

Example: "Első tárgyalás elhalasztva"
description
string | null

Entry details as rich HTML, sanitised server-side.

location
string | null

Where it happened (plain text), e.g. a court room; nullable.

event_date
string <date> | null

Day of the entry, YYYY-MM-DD. Flowyer records the date only, no time of day.

user_id
integer | null

Author (the user who created the entry).

created_by
string | null

Display name of the author.

last_editor_id
integer | null

User who last edited the entry.

last_editor
string | null

Display name of the last editor.

created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to litigation.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the entry through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Create litigation history entry

POST /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.

Scope
litigation:create
Flowyer permission
cases:create

Path parameters

idrequired
integer>= 1

Matter the entry belongs to (must be visible).

Example: 1234

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

titlerequired
stringat least 1 charactersat most 2000 characters

Entry title, at most 2000 characters. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
Example: "Első tárgyalás elhalasztva"
description
string | null

Entry details. Rich HTML, sanitised server-side with the knowledge-base profile.

location
string | nullat most 255 characters

Where it happened (plain text, at most 255 characters); "<" and ">" are not allowed.

Pattern: ^[^<>]*$
Example: "Fővárosi Törvényszék, 215. tárgyaló"
event_date
string <date> | null

Day of the entry, YYYY-MM-DD. Defaults to today (office time zone).

Example: "2026-11-15"
idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer
Example: 4521
title
string

Entry title. Plain text.

Example: "Első tárgyalás elhalasztva"
description
string | null

Entry details as rich HTML, sanitised server-side.

location
string | null

Where it happened (plain text), e.g. a court room; nullable.

event_date
string <date> | null

Day of the entry, YYYY-MM-DD. Flowyer records the date only, no time of day.

user_id
integer | null

Author (the user who created the entry).

created_by
string | null

Display name of the author.

last_editor_id
integer | null

User who last edited the entry.

last_editor
string | null

Display name of the last editor.

created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to litigation.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the entry through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer
Example: 4521
title
string

Entry title. Plain text.

Example: "Első tárgyalás elhalasztva"
description
string | null

Entry details as rich HTML, sanitised server-side.

location
string | null

Where it happened (plain text), e.g. a court room; nullable.

event_date
string <date> | null

Day of the entry, YYYY-MM-DD. Flowyer records the date only, no time of day.

user_id
integer | null

Author (the user who created the entry).

created_by
string | null

Display name of the author.

last_editor_id
integer | null

User who last edited the entry.

last_editor
string | null

Display name of the last editor.

created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to litigation.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the entry through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Update litigation history entry

PATCH /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.

Scope
litigation:update
Flowyer permission
cases:modify

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

title
string | nullat least 1 charactersat most 2000 characters

New title, at most 2000 characters (plain text; "<"/">" not allowed). Omit to keep the current one.

Pattern: ^[^<>]*$
Example: "Ítélethirdetés elhalasztva"
description
string | null

New details (rich HTML, sanitised). Omit to keep the current one; null/empty clears it.

location
string | nullat most 255 characters

New location (plain text, at most 255 characters; "<"/">" not allowed). Omit to keep it; null/empty clears it.

Pattern: ^[^<>]*$
event_date
string <date> | null

New event day, YYYY-MM-DD. Omit to keep it; null clears it.

Responses

200

Updated.

datarequired
object
Fields of data
id
integer
Example: 4521
title
string

Entry title. Plain text.

Example: "Első tárgyalás elhalasztva"
description
string | null

Entry details as rich HTML, sanitised server-side.

location
string | null

Where it happened (plain text), e.g. a court room; nullable.

event_date
string <date> | null

Day of the entry, YYYY-MM-DD. Flowyer records the date only, no time of day.

user_id
integer | null

Author (the user who created the entry).

created_by
string | null

Display name of the author.

last_editor_id
integer | null

User who last edited the entry.

last_editor
string | null

Display name of the last editor.

created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to litigation.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the entry through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Mail register

List mail register

GET /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.

Scope
mail_register:read
Flowyer permission
mail:read

Query parameters

matter_id
integer>= 1

Only entries of this matter (must be visible to the user).

Example: 1234
direction
string

Only incoming (received) or outgoing (sent) entries.

Allowed values: "incoming" "outgoing"
q
stringat most 100 characters

Free-text search over subject, sender, addressee, recipient and tracking number (case ignored).

Example: "Kúria"
date_from
string <date>

Only entries whose date is on or after this day (YYYY-MM-DD).

Example: "2026-01-01"
date_to
string <date>

Only entries whose date is on or before this day (YYYY-MM-DD).

Example: "2026-12-31"
updated_since
string <date-time>

Only entries changed at or after this time (ISO 8601).

Example: "2026-10-01T00:00:00+02:00"
include_archived
boolean

Include entries of archived matters.

Default: false
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer

Mail-register entry id (use with mail_register.get / .update).

Example: 4321
matter
object | null

The matter the entry is filed against.

Fields of matter
id
integer
case_number
string
direction
string

incoming = received, outgoing = sent.

Allowed values: "incoming" "outgoing"
date
string <date> | null

Postal/recorded date, YYYY-MM-DD (no time of day).

subject
string | null
sender
string | null

Feladó: the sender (free text).

to
string | null

Címzett: the addressee (free text).

recipient
string | null

For an incoming mail: the display name of the colleague who received it. For an outgoing mail: the free-text addressee/received-by.

recipient_user_id
integer | null

Set only for an incoming mail whose recipient is a colleague (a user id); null for an outgoing mail.

electronic
boolean

True when the item was recorded as electronic (type_of_mail).

delivery_method_id
integer | null

Id of the delivery method (mail_delivery_methods).

delivery_method
string | null

Localized delivery-method label (Posta, Cégkapu, Perkapu, ...), or the electronic/non-electronic fallback for a legacy row.

tracking_no
string | null

Azonosító: tracking number.

folder_path
string | null
recorded_by_user_id
integer | null

The colleague who recorded the entry.

recorded_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to mail_register.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the entry through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get mail register entry

GET /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.

Scope
mail_register:read
Flowyer permission
mail:read

Path parameters

idrequired
integer>= 1

Mail-register entry id.

Example: 4321

Responses

200

OK.

datarequired
object
Fields of data
id
integer

Mail-register entry id (use with mail_register.get / .update).

Example: 4321
matter
object | null

The matter the entry is filed against.

Fields of matter
id
integer
case_number
string
direction
string

incoming = received, outgoing = sent.

Allowed values: "incoming" "outgoing"
date
string <date> | null

Postal/recorded date, YYYY-MM-DD (no time of day).

subject
string | null
sender
string | null

Feladó: the sender (free text).

to
string | null

Címzett: the addressee (free text).

recipient
string | null

For an incoming mail: the display name of the colleague who received it. For an outgoing mail: the free-text addressee/received-by.

recipient_user_id
integer | null

Set only for an incoming mail whose recipient is a colleague (a user id); null for an outgoing mail.

electronic
boolean

True when the item was recorded as electronic (type_of_mail).

delivery_method_id
integer | null

Id of the delivery method (mail_delivery_methods).

delivery_method
string | null

Localized delivery-method label (Posta, Cégkapu, Perkapu, ...), or the electronic/non-electronic fallback for a legacy row.

tracking_no
string | null

Azonosító: tracking number.

folder_path
string | null
recorded_by_user_id
integer | null

The colleague who recorded the entry.

recorded_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to mail_register.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the entry through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
    }
  }
}

Create mail register entry

POST /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.

Scope
mail_register:create
Flowyer permission
mail:create

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

matter_idrequired
integer>= 1

Matter the entry belongs to (must be visible).

Example: 1234
directionrequired
string

incoming = received, outgoing = sent.

Allowed values: "incoming" "outgoing"
daterequired
string <date>

Postal/recorded date, YYYY-MM-DD (no time of day).

Example: "2026-11-03"
subjectrequired
stringat least 1 charactersat most 1000 characters

Subject of the correspondence. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
Example: "Fellebbezés a Kúriához"
sender
string | nullat most 1000 characters

Feladó: the sender (free text). Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
to
string | nullat most 1000 characters

Címzett: the addressee (free text). Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
recipient
string | nullat most 1000 characters

Outgoing mail: the free-text addressee/received-by. Not allowed together with recipient_user_id. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
recipient_user_id
integer | null>= 1

Incoming mail: the colleague who received it (a user id from colleagues.list), stored as the numeric recipient. Not allowed together with recipient.

electronic
boolean

Whether the item is electronic (type_of_mail); independent of the delivery method. Default false.

Default: false
delivery_method_id
integer | null>= 1

Delivery method id (mail_delivery_methods): e.g. 1 Posta, 4 Cégkapu, 7 Perkapu. 422 when unknown.

tracking_no
string | nullat most 255 characters

Azonosító: tracking number. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
folder_path
string | nullat most 1000 characters
Pattern: ^[^<>]*$
idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer

Mail-register entry id (use with mail_register.get / .update).

Example: 4321
matter
object | null

The matter the entry is filed against.

Fields of matter
id
integer
case_number
string
direction
string

incoming = received, outgoing = sent.

Allowed values: "incoming" "outgoing"
date
string <date> | null

Postal/recorded date, YYYY-MM-DD (no time of day).

subject
string | null
sender
string | null

Feladó: the sender (free text).

to
string | null

Címzett: the addressee (free text).

recipient
string | null

For an incoming mail: the display name of the colleague who received it. For an outgoing mail: the free-text addressee/received-by.

recipient_user_id
integer | null

Set only for an incoming mail whose recipient is a colleague (a user id); null for an outgoing mail.

electronic
boolean

True when the item was recorded as electronic (type_of_mail).

delivery_method_id
integer | null

Id of the delivery method (mail_delivery_methods).

delivery_method
string | null

Localized delivery-method label (Posta, Cégkapu, Perkapu, ...), or the electronic/non-electronic fallback for a legacy row.

tracking_no
string | null

Azonosító: tracking number.

folder_path
string | null
recorded_by_user_id
integer | null

The colleague who recorded the entry.

recorded_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to mail_register.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the entry through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer

Mail-register entry id (use with mail_register.get / .update).

Example: 4321
matter
object | null

The matter the entry is filed against.

Fields of matter
id
integer
case_number
string
direction
string

incoming = received, outgoing = sent.

Allowed values: "incoming" "outgoing"
date
string <date> | null

Postal/recorded date, YYYY-MM-DD (no time of day).

subject
string | null
sender
string | null

Feladó: the sender (free text).

to
string | null

Címzett: the addressee (free text).

recipient
string | null

For an incoming mail: the display name of the colleague who received it. For an outgoing mail: the free-text addressee/received-by.

recipient_user_id
integer | null

Set only for an incoming mail whose recipient is a colleague (a user id); null for an outgoing mail.

electronic
boolean

True when the item was recorded as electronic (type_of_mail).

delivery_method_id
integer | null

Id of the delivery method (mail_delivery_methods).

delivery_method
string | null

Localized delivery-method label (Posta, Cégkapu, Perkapu, ...), or the electronic/non-electronic fallback for a legacy row.

tracking_no
string | null

Azonosító: tracking number.

folder_path
string | null
recorded_by_user_id
integer | null

The colleague who recorded the entry.

recorded_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to mail_register.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the entry through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Update mail register entry

PATCH /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.

Scope
mail_register:update
Flowyer permission
mail:modify

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

direction
string | null

incoming = received, outgoing = sent.

Allowed values: "incoming" "outgoing"
date
string <date> | null

Postal/recorded date, YYYY-MM-DD.

subject
string | nullat least 1 charactersat most 1000 characters

Subject. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
sender
string | nullat most 1000 characters

Feladó: the sender (free text). Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
to
string | nullat most 1000 characters

Címzett: the addressee (free text). Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
recipient
string | nullat most 1000 characters

Outgoing mail: the free-text addressee. Not allowed together with recipient_user_id. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
recipient_user_id
integer | null>= 1

Incoming mail: the receiving colleague (a user id), stored as the numeric recipient. Not allowed together with recipient.

electronic
boolean | null

Whether the item is electronic (type_of_mail).

delivery_method_id
integer | null>= 1

Delivery method id (mail_delivery_methods); 422 when unknown.

tracking_no
string | nullat most 255 characters

Azonosító: tracking number. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
folder_path
string | nullat most 1000 characters
Pattern: ^[^<>]*$

Responses

200

Updated.

datarequired
object
Fields of data
id
integer

Mail-register entry id (use with mail_register.get / .update).

Example: 4321
matter
object | null

The matter the entry is filed against.

Fields of matter
id
integer
case_number
string
direction
string

incoming = received, outgoing = sent.

Allowed values: "incoming" "outgoing"
date
string <date> | null

Postal/recorded date, YYYY-MM-DD (no time of day).

subject
string | null
sender
string | null

Feladó: the sender (free text).

to
string | null

Címzett: the addressee (free text).

recipient
string | null

For an incoming mail: the display name of the colleague who received it. For an outgoing mail: the free-text addressee/received-by.

recipient_user_id
integer | null

Set only for an incoming mail whose recipient is a colleague (a user id); null for an outgoing mail.

electronic
boolean

True when the item was recorded as electronic (type_of_mail).

delivery_method_id
integer | null

Id of the delivery method (mail_delivery_methods).

delivery_method
string | null

Localized delivery-method label (Posta, Cégkapu, Perkapu, ...), or the electronic/non-electronic fallback for a legacy row.

tracking_no
string | null

Azonosító: tracking number.

folder_path
string | null
recorded_by_user_id
integer | null

The colleague who recorded the entry.

recorded_by
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to mail_register.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the entry through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Comments

List matter comments

GET /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.

Scope
comments:read
Flowyer permission
cases:read

Path parameters

idrequired
integer>= 1

Matter id (must be visible).

Example: 1234

Query parameters

top_level_only
boolean

Only top-level comments (exclude replies). Default false.

Default: false
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer
Example: 9876
matter_id
integer

The matter (= case_id) the comment belongs to.

parent_id
integer | null

The top-level comment this replies to; null for a top-level comment. Replies are one level deep.

author_user_id
integer | null

User id of the author (null for a legacy import).

author_name
string | null

Display name of the author, or null.

body
string

The comment as stored: rich HTML, already HTMLPurifier-cleaned for comments written through this API.

created_at
string <date-time> | null

When the comment was posted, ISO 8601 in the office zone.

updated_at
string <date-time> | null

Optimistic-lock token: pass it to comments.update as expected_updated_at. Not a display time; null for a legacy comment without one.

edited
boolean

True when the comment has been edited since it was posted.

paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Post matter comment

POST /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.

Scope
comments:create
Flowyer permission
cases:create

Path parameters

idrequired
integer>= 1

Matter id (must be visible).

Example: 1234

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

bodyrequired
stringat least 1 characters

The comment, rich HTML (as TinyMCE produces). Server-side HTMLPurifier-cleaned before store. At most 100000 characters.

Example: "<p>Egyetértek, a határidő tartható.</p>"
parent_id
integer | null>= 1

Reply to this TOP-LEVEL comment of the SAME matter (from comments.list, parent_id = null); omit for a new top-level comment.

idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer
Example: 9876
matter_id
integer

The matter (= case_id) the comment belongs to.

parent_id
integer | null

The top-level comment this replies to; null for a top-level comment. Replies are one level deep.

author_user_id
integer | null

User id of the author (null for a legacy import).

author_name
string | null

Display name of the author, or null.

body
string

The comment as stored: rich HTML, already HTMLPurifier-cleaned for comments written through this API.

created_at
string <date-time> | null

When the comment was posted, ISO 8601 in the office zone.

updated_at
string <date-time> | null

Optimistic-lock token: pass it to comments.update as expected_updated_at. Not a display time; null for a legacy comment without one.

edited
boolean

True when the comment has been edited since it was posted.

created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer
Example: 9876
matter_id
integer

The matter (= case_id) the comment belongs to.

parent_id
integer | null

The top-level comment this replies to; null for a top-level comment. Replies are one level deep.

author_user_id
integer | null

User id of the author (null for a legacy import).

author_name
string | null

Display name of the author, or null.

body
string

The comment as stored: rich HTML, already HTMLPurifier-cleaned for comments written through this API.

created_at
string <date-time> | null

When the comment was posted, ISO 8601 in the office zone.

updated_at
string <date-time> | null

Optimistic-lock token: pass it to comments.update as expected_updated_at. Not a display time; null for a legacy comment without one.

edited
boolean

True when the comment has been edited since it was posted.

created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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>"
}'

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
}

Edit own matter comment

PATCH /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.

Scope
comments:update
Flowyer permission
cases:modify

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

bodyrequired
stringat least 1 characters

New comment body, rich HTML (as TinyMCE produces). Server-side HTMLPurifier-cleaned before store. At most 100000 characters.

Example: "<p>Pontosítás: a határidő november 15.</p>"

Responses

200

Updated.

datarequired
object
Fields of data
id
integer
Example: 9876
matter_id
integer

The matter (= case_id) the comment belongs to.

parent_id
integer | null

The top-level comment this replies to; null for a top-level comment. Replies are one level deep.

author_user_id
integer | null

User id of the author (null for a legacy import).

author_name
string | null

Display name of the author, or null.

body
string

The comment as stored: rich HTML, already HTMLPurifier-cleaned for comments written through this API.

created_at
string <date-time> | null

When the comment was posted, ISO 8601 in the office zone.

updated_at
string <date-time> | null

Optimistic-lock token: pass it to comments.update as expected_updated_at. Not a display time; null for a legacy comment without one.

edited
boolean

True when the comment has been edited since it was posted.

updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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>"
}'

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
}

Trials

List matter hearings

GET /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.

Scope
trials:read
Flowyer permission
trials:read

Path parameters

idrequired
integer>= 1

Matter id.

Example: 1234

Query parameters

from
string <date>

Only hearings on or after this day (YYYY-MM-DD), in the office time zone.

Example: "2026-01-01"
to
string <date>

Only hearings on or before this day (YYYY-MM-DD), in the office time zone.

Example: "2026-12-31"
updated_since
string <date-time>

Only hearings changed at or after this ISO 8601 time.

limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer
Example: 4321
matter
object

The matter (case) the hearing belongs to.

Fields of matter
id
integer
case_number
string
date
string <date-time> | null

Hearing date and time, ISO 8601 in the office time zone. It is stored in UTC, so compare instants rather than the wall-clock text.

Example: "2026-03-02T09:00:00+01:00"
participation
string | null

Whether a colleague's attendance is required: "required", "not_required", or null when unset.

Allowed values: "required" "not_required"
court
object | null

The court of the hearing, resolved from the shared court registry; null when no court is set.

Fields of court
id
integer
name
string
city
string | null
zip
string | null
address
string | null
source
string

Always "registry": the court is a reference to the shared court registry (courts.list), not free text.

place
string | null

Room or location of the hearing. Plain text.

name_of_the_judge
string | null

Name of the judge. Plain text.

notes
string | null

Free-text notes on the hearing. Plain text.

responsibles
object[]

Colleagues attending the hearing (ids from colleagues.list).

Fields of responsibles
user_id
integer
name
string | null
recorded_by
object | null

The colleague who recorded the hearing.

Fields of recorded_by
user_id
integer
name
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to trials.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get hearing

GET /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.

Scope
trials:read
Flowyer permission
trials:read

Path parameters

idrequired
integer>= 1

Hearing id.

Example: 4321

Responses

200

OK.

datarequired
object
Fields of data
id
integer
Example: 4321
matter
object

The matter (case) the hearing belongs to.

Fields of matter
id
integer
case_number
string
date
string <date-time> | null

Hearing date and time, ISO 8601 in the office time zone. It is stored in UTC, so compare instants rather than the wall-clock text.

Example: "2026-03-02T09:00:00+01:00"
participation
string | null

Whether a colleague's attendance is required: "required", "not_required", or null when unset.

Allowed values: "required" "not_required"
court
object | null

The court of the hearing, resolved from the shared court registry; null when no court is set.

Fields of court
id
integer
name
string
city
string | null
zip
string | null
address
string | null
source
string

Always "registry": the court is a reference to the shared court registry (courts.list), not free text.

place
string | null

Room or location of the hearing. Plain text.

name_of_the_judge
string | null

Name of the judge. Plain text.

notes
string | null

Free-text notes on the hearing. Plain text.

responsibles
object[]

Colleagues attending the hearing (ids from colleagues.list).

Fields of responsibles
user_id
integer
name
string | null
recorded_by
object | null

The colleague who recorded the hearing.

Fields of recorded_by
user_id
integer
name
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to trials.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
    }
  }
}

Create hearing

POST /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.

Scope
trials:create
Flowyer permission
trials:create

Path parameters

idrequired
integer>= 1

Matter the hearing belongs to (must be visible and not archived).

Example: 1234

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

daterequired
string <date-time>

Hearing date and time, ISO 8601. With an offset it is honoured; without one it is read in the office time zone. Stored in UTC.

Example: "2026-03-02T09:00:00+01:00"
court_id
integer | null>= 1

Court of the hearing: an id from courts.list (the shared court registry). Omit when the court is not known.

Example: 42
participation
string | null

Whether a colleague's attendance is required. Omit to leave it unset.

Allowed values: "required" "not_required"
place
string | nullat most 1000 characters

Room or location of the hearing. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
Example: "II. emelet 205."
name_of_the_judge
string | nullat most 255 characters

Name of the judge. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
notes
string | nullat most 10000 characters

Free-text notes on the hearing. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
responsible_user_ids
integer[] | null

Colleague user ids (from colleagues.list) attending the hearing.

idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer
Example: 4321
matter
object

The matter (case) the hearing belongs to.

Fields of matter
id
integer
case_number
string
date
string <date-time> | null

Hearing date and time, ISO 8601 in the office time zone. It is stored in UTC, so compare instants rather than the wall-clock text.

Example: "2026-03-02T09:00:00+01:00"
participation
string | null

Whether a colleague's attendance is required: "required", "not_required", or null when unset.

Allowed values: "required" "not_required"
court
object | null

The court of the hearing, resolved from the shared court registry; null when no court is set.

Fields of court
id
integer
name
string
city
string | null
zip
string | null
address
string | null
source
string

Always "registry": the court is a reference to the shared court registry (courts.list), not free text.

place
string | null

Room or location of the hearing. Plain text.

name_of_the_judge
string | null

Name of the judge. Plain text.

notes
string | null

Free-text notes on the hearing. Plain text.

responsibles
object[]

Colleagues attending the hearing (ids from colleagues.list).

Fields of responsibles
user_id
integer
name
string | null
recorded_by
object | null

The colleague who recorded the hearing.

Fields of recorded_by
user_id
integer
name
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to trials.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer
Example: 4321
matter
object

The matter (case) the hearing belongs to.

Fields of matter
id
integer
case_number
string
date
string <date-time> | null

Hearing date and time, ISO 8601 in the office time zone. It is stored in UTC, so compare instants rather than the wall-clock text.

Example: "2026-03-02T09:00:00+01:00"
participation
string | null

Whether a colleague's attendance is required: "required", "not_required", or null when unset.

Allowed values: "required" "not_required"
court
object | null

The court of the hearing, resolved from the shared court registry; null when no court is set.

Fields of court
id
integer
name
string
city
string | null
zip
string | null
address
string | null
source
string

Always "registry": the court is a reference to the shared court registry (courts.list), not free text.

place
string | null

Room or location of the hearing. Plain text.

name_of_the_judge
string | null

Name of the judge. Plain text.

notes
string | null

Free-text notes on the hearing. Plain text.

responsibles
object[]

Colleagues attending the hearing (ids from colleagues.list).

Fields of responsibles
user_id
integer
name
string | null
recorded_by
object | null

The colleague who recorded the hearing.

Fields of recorded_by
user_id
integer
name
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to trials.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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."
}'

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
}

Update hearing

PATCH /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.

Scope
trials:update
Flowyer permission
trials:modify

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

date
string <date-time>

New hearing date and time, ISO 8601 (an offset is honoured, a naive time is read in the office zone). Stored in UTC.

Example: "2026-03-02T09:00:00+01:00"
court_id
integer | null>= 1

New court id from courts.list; null to clear the court.

participation
string | null

Whether a colleague's attendance is required; null to clear it.

Allowed values: "required" "not_required"
place
string | nullat most 1000 characters

Room or location. Plain text; "<" and ">" are not allowed. "" clears it.

Pattern: ^[^<>]*$
name_of_the_judge
string | nullat most 255 characters

Name of the judge. Plain text; "<" and ">" are not allowed. "" clears it.

Pattern: ^[^<>]*$
notes
string | nullat most 10000 characters

Free-text notes. Plain text; "<" and ">" are not allowed. "" clears it.

Pattern: ^[^<>]*$
responsible_user_ids
integer[] | null

Full replacement set of attendee user ids (from colleagues.list). Omit to leave the attendees unchanged.

Responses

200

Updated.

datarequired
object
Fields of data
id
integer
Example: 4321
matter
object

The matter (case) the hearing belongs to.

Fields of matter
id
integer
case_number
string
date
string <date-time> | null

Hearing date and time, ISO 8601 in the office time zone. It is stored in UTC, so compare instants rather than the wall-clock text.

Example: "2026-03-02T09:00:00+01:00"
participation
string | null

Whether a colleague's attendance is required: "required", "not_required", or null when unset.

Allowed values: "required" "not_required"
court
object | null

The court of the hearing, resolved from the shared court registry; null when no court is set.

Fields of court
id
integer
name
string
city
string | null
zip
string | null
address
string | null
source
string

Always "registry": the court is a reference to the shared court registry (courts.list), not free text.

place
string | null

Room or location of the hearing. Plain text.

name_of_the_judge
string | null

Name of the judge. Plain text.

notes
string | null

Free-text notes on the hearing. Plain text.

responsibles
object[]

Colleagues attending the hearing (ids from colleagues.list).

Fields of responsibles
user_id
integer
name
string | null
recorded_by
object | null

The colleague who recorded the hearing.

Fields of recorded_by
user_id
integer
name
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to trials.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Courts

List courts

GET /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.

Scope
courts:read

Query parameters

q
stringat most 255 characters

A court name to look up, as heard or dictated, in any word order. Omit it to list every court.

Example: "Fővárosi Törvényszék"
limit
integer>= 1<= 500

Maximum courts to return (default 200).

Default: 200

Responses

200

OK.

datarequired
object
Fields of data
status
string | null

Only 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.

Allowed values: "exact" "similar" "none"
courts
object[]

Courts, best match first with q, otherwise by name.

Fields of courts
id
integer

Court id: pass it as court_id to trials.create / trials.update.

name
string

Court name as shown in Flowyer.

city
string | null
zip
string | null
address
string | null
total
integer

How many courts matched (all courts without q) before the list was cut to limit.

truncated
boolean

True 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

Response samples

{
  "data": {
    "status": "exact",
    "courts": [
      {
        "id": 1234,
        "name": "string",
        "city": "string",
        "zip": "string",
        "address": "string"
      }
    ],
    "total": 1,
    "truncated": true
  }
}

Hr

List staff leaves

GET /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.

Scope
hr:read
Flowyer permission
hr-admin:read

Query parameters

user_id
integer>= 1

Only this colleague's leaves. A central user id (from colleagues.list), matched to the employee behind the leave.

Example: 1234
on_date
string <date>

Who is on leave on this exact day (start_date <= on_date <= end_date). When set, it overrides date_from, date_to and timeframe.

date_from
string <date>

Only leaves still running on or after this date (end_date >= date_from). Defaults to today, so fully past leaves are excluded by default.

date_to
string <date>

Only leaves starting on or before this date (start_date <= date_to). Omit for open-ended future.

timeframe
string

Extra start-date constraint versus today: current = already started (ongoing), upcoming = not yet started, all = no extra. Ignored when on_date is set.

Allowed values: "current" "upcoming" "all"
Default: "all"
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer

Leave request id (a handle only; not sensitive).

user_id
integer

The colleague on leave (central user id).

user
string | null

The colleague's display name.

start_date
string <date>

First day of leave (YYYY-MM-DD).

end_date
string <date>

Last day of leave, inclusive (YYYY-MM-DD).

days_count
number | null

Working days of leave. Does not reveal the type.

category
string

Privacy-redacted leave type: annual and sick both become "leave" (sick is never identifiable), unpaid stays "unpaid". The raw leave type is never exposed.

Allowed values: "leave" "unpaid"
status
string

Always "approved" - only approved leaves are returned; pending, rejected and cancelled never surface.

Allowed values: "approved"
substitute_user_id
integer | null

Covering colleague for this leave (central user id), or null.

substitute_user
string | null

Covering colleague's display name, or null.

paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Calendar

List calendar events

GET /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.

Scope
calendar:read
Flowyer permission
calendar-office:read

Query parameters

fromrequired
string <date-time>

Range start (inclusive), ISO 8601. Events overlapping [from, to] are returned. A time with no offset is read in the office zone.

Example: "2026-03-01T00:00:00+01:00"
torequired
string <date-time>

Range end (inclusive), ISO 8601. Must be on or after from (else 422) and at most 366 days after it.

Example: "2026-03-31T23:59:59+01:00"
calendar_ids
integer[]

Narrow 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_recurring
boolean

Include recurring master events whose rule overlaps the range (returned un-expanded, is_recurring = true). Default true.

Default: true
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer
Example: 5501
calendar
object

The calendar the event belongs to.

Fields of calendar
id
integer | null

Container calendar id, or null for the implicit shared office calendar (a legacy event with no container).

name
string | null
type
string

"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.

Allowed values: "office" "personal"
owner
object | null

Owner of a personal calendar; null for the office calendar.

Fields of owner
user_id
integer
name
string | null
title
string | null

Event title. Plain text.

description
string | null

Event description. Plain text.

location
string | null

Location. Plain text.

participants
string | null

Participants, free text. Plain text.

start
string | null

An all-day event: the start date "YYYY-MM-DD". A timed event: ISO 8601 in the office time zone (the stored UTC instant).

Example: "2026-03-02T09:00:00+01:00"
end
string | null

Same 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_day
boolean

True for an all-day event (start/end are bare dates); false for a timed event (start/end are ISO 8601 instants).

color
string | null

Display colour, e.g. "#2c7be5".

is_recurring
boolean

True when the event is a recurring master. Recurring events are returned once, NOT expanded into occurrences, and cannot be modified through calendar.events.update.

recurrence
object | null

The recurrence rule of a recurring master, or null for a single event. Read-only.

Fields of recurrence
freq
string | null

daily | weekly | monthly | yearly.

interval
integer | null
byweekday
string | null

Comma-separated weekday codes (e.g. "MO,WE,FR").

until
string | null
count
integer | null
created_by
object | null

The colleague who created the event.

Fields of created_by
user_id
integer
name
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to calendar.events.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get calendar event

GET /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.

Scope
calendar:read
Flowyer permission
calendar-office:read

Path parameters

idrequired
integer>= 1

Event id.

Example: 5501

Responses

200

OK.

datarequired
object
Fields of data
id
integer
Example: 5501
calendar
object

The calendar the event belongs to.

Fields of calendar
id
integer | null

Container calendar id, or null for the implicit shared office calendar (a legacy event with no container).

name
string | null
type
string

"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.

Allowed values: "office" "personal"
owner
object | null

Owner of a personal calendar; null for the office calendar.

Fields of owner
user_id
integer
name
string | null
title
string | null

Event title. Plain text.

description
string | null

Event description. Plain text.

location
string | null

Location. Plain text.

participants
string | null

Participants, free text. Plain text.

start
string | null

An all-day event: the start date "YYYY-MM-DD". A timed event: ISO 8601 in the office time zone (the stored UTC instant).

Example: "2026-03-02T09:00:00+01:00"
end
string | null

Same 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_day
boolean

True for an all-day event (start/end are bare dates); false for a timed event (start/end are ISO 8601 instants).

color
string | null

Display colour, e.g. "#2c7be5".

is_recurring
boolean

True when the event is a recurring master. Recurring events are returned once, NOT expanded into occurrences, and cannot be modified through calendar.events.update.

recurrence
object | null

The recurrence rule of a recurring master, or null for a single event. Read-only.

Fields of recurrence
freq
string | null

daily | weekly | monthly | yearly.

interval
integer | null
byweekday
string | null

Comma-separated weekday codes (e.g. "MO,WE,FR").

until
string | null
count
integer | null
created_by
object | null

The colleague who created the event.

Fields of created_by
user_id
integer
name
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to calendar.events.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
    }
  }
}

Create calendar event

POST /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.

Scope
calendar:create
Flowyer permission
calendar-office:create

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

titlerequired
stringat least 1 charactersat most 255 characters

Event title. Plain text; "<" and ">" are not allowed (it flows unescaped into the reminder notification).

Pattern: ^[^<>]*$
Example: "Ügyfél egyeztetés"
startrequired
string

All-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.

Example: "2026-03-02T09:00:00+01:00"
end
string | null

Same encoding as start. Default = start. For an all-day event the end date is INCLUSIVE. Must not be before start (else 422).

all_day
boolean

Whether the event is all-day. Default false (timed).

Default: false
calendar_id
integer | null>= 1

Target container calendar; default = the office calendar. You must hold edit rights on it (else 403). Only on a tenant with the personal-calendar feature.

location
string | nullat most 1000 characters

Location. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
description
string | nullat most 10000 characters

Description, plain text (not HTML); "<" and ">" are not allowed.

Pattern: ^[^<>]*$
participants
string | nullat most 1000 characters

Participants, free text. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
color
string | nullat most 32 characters

Display colour, e.g. "#2c7be5".

Pattern: ^#?[A-Za-z0-9_-]{1,31}$
idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer
Example: 5501
calendar
object

The calendar the event belongs to.

Fields of calendar
id
integer | null

Container calendar id, or null for the implicit shared office calendar (a legacy event with no container).

name
string | null
type
string

"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.

Allowed values: "office" "personal"
owner
object | null

Owner of a personal calendar; null for the office calendar.

Fields of owner
user_id
integer
name
string | null
title
string | null

Event title. Plain text.

description
string | null

Event description. Plain text.

location
string | null

Location. Plain text.

participants
string | null

Participants, free text. Plain text.

start
string | null

An all-day event: the start date "YYYY-MM-DD". A timed event: ISO 8601 in the office time zone (the stored UTC instant).

Example: "2026-03-02T09:00:00+01:00"
end
string | null

Same 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_day
boolean

True for an all-day event (start/end are bare dates); false for a timed event (start/end are ISO 8601 instants).

color
string | null

Display colour, e.g. "#2c7be5".

is_recurring
boolean

True when the event is a recurring master. Recurring events are returned once, NOT expanded into occurrences, and cannot be modified through calendar.events.update.

recurrence
object | null

The recurrence rule of a recurring master, or null for a single event. Read-only.

Fields of recurrence
freq
string | null

daily | weekly | monthly | yearly.

interval
integer | null
byweekday
string | null

Comma-separated weekday codes (e.g. "MO,WE,FR").

until
string | null
count
integer | null
created_by
object | null

The colleague who created the event.

Fields of created_by
user_id
integer
name
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to calendar.events.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer
Example: 5501
calendar
object

The calendar the event belongs to.

Fields of calendar
id
integer | null

Container calendar id, or null for the implicit shared office calendar (a legacy event with no container).

name
string | null
type
string

"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.

Allowed values: "office" "personal"
owner
object | null

Owner of a personal calendar; null for the office calendar.

Fields of owner
user_id
integer
name
string | null
title
string | null

Event title. Plain text.

description
string | null

Event description. Plain text.

location
string | null

Location. Plain text.

participants
string | null

Participants, free text. Plain text.

start
string | null

An all-day event: the start date "YYYY-MM-DD". A timed event: ISO 8601 in the office time zone (the stored UTC instant).

Example: "2026-03-02T09:00:00+01:00"
end
string | null

Same 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_day
boolean

True for an all-day event (start/end are bare dates); false for a timed event (start/end are ISO 8601 instants).

color
string | null

Display colour, e.g. "#2c7be5".

is_recurring
boolean

True when the event is a recurring master. Recurring events are returned once, NOT expanded into occurrences, and cannot be modified through calendar.events.update.

recurrence
object | null

The recurrence rule of a recurring master, or null for a single event. Read-only.

Fields of recurrence
freq
string | null

daily | weekly | monthly | yearly.

interval
integer | null
byweekday
string | null

Comma-separated weekday codes (e.g. "MO,WE,FR").

until
string | null
count
integer | null
created_by
object | null

The colleague who created the event.

Fields of created_by
user_id
integer
name
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to calendar.events.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Update calendar event

PATCH /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.

Scope
calendar:update
Flowyer permission
calendar-office:modify

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

title
stringat least 1 charactersat most 255 characters

New title. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
start
string

New 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.

end
string | null

New 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_day
boolean

If sent, start/end are re-encoded accordingly; if omitted, the row's current all_day is used.

location
string | nullat most 1000 characters

Location. Plain text; "<" and ">" are not allowed. "" clears it.

Pattern: ^[^<>]*$
description
string | nullat most 10000 characters

Description, plain text (not HTML); "<" and ">" are not allowed. "" clears it.

Pattern: ^[^<>]*$
participants
string | nullat most 1000 characters

Participants, free text. Plain text; "<" and ">" are not allowed. "" clears it.

Pattern: ^[^<>]*$
color
string | nullat most 32 characters

Display colour, e.g. "#2c7be5". "" clears it.

Pattern: ^#?[A-Za-z0-9_-]{1,31}$

Responses

200

Updated.

datarequired
object
Fields of data
id
integer
Example: 5501
calendar
object

The calendar the event belongs to.

Fields of calendar
id
integer | null

Container calendar id, or null for the implicit shared office calendar (a legacy event with no container).

name
string | null
type
string

"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.

Allowed values: "office" "personal"
owner
object | null

Owner of a personal calendar; null for the office calendar.

Fields of owner
user_id
integer
name
string | null
title
string | null

Event title. Plain text.

description
string | null

Event description. Plain text.

location
string | null

Location. Plain text.

participants
string | null

Participants, free text. Plain text.

start
string | null

An all-day event: the start date "YYYY-MM-DD". A timed event: ISO 8601 in the office time zone (the stored UTC instant).

Example: "2026-03-02T09:00:00+01:00"
end
string | null

Same 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_day
boolean

True for an all-day event (start/end are bare dates); false for a timed event (start/end are ISO 8601 instants).

color
string | null

Display colour, e.g. "#2c7be5".

is_recurring
boolean

True when the event is a recurring master. Recurring events are returned once, NOT expanded into occurrences, and cannot be modified through calendar.events.update.

recurrence
object | null

The recurrence rule of a recurring master, or null for a single event. Read-only.

Fields of recurrence
freq
string | null

daily | weekly | monthly | yearly.

interval
integer | null
byweekday
string | null

Comma-separated weekday codes (e.g. "MO,WE,FR").

until
string | null
count
integer | null
created_by
object | null

The colleague who created the event.

Fields of created_by
user_id
integer
name
string | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to calendar.events.update as expected_updated_at.

created_via_api
object | null

Present when an external agent created the record through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Bookings

List bookings

GET /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.

Scope
bookings:read

Query parameters

from
string <date>

Only bookings on or after this day (YYYY-MM-DD), matched literally against the booking-zone date.

Example: "2026-01-01"
to
string <date>

Only bookings on or before this day (YYYY-MM-DD).

Example: "2026-12-31"
user_id
integer>= 1

Filter to one lawyer (user_id from colleagues.list). Omit for every lawyer of the office.

status
string

Filter by status.

Allowed values: "pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired"
updated_since
string <date-time>

Only bookings changed at or after this ISO 8601 time (updated_at is office-zone naive).

limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer
Example: 4321
booking_number
string

Human booking number, e.g. "#B-0001".

status
string

pending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.

Allowed values: "pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired"
source
string

Where the booking came from. An API-created booking is "manual".

Allowed values: "flowyer" "website" "embed_widget" "manual"
lawyer
object | null

The lawyer (user_id from colleagues.list) the appointment is with.

Fields of lawyer
user_id
integer
name
string | null
date
string <date> | null

Calendar date in the booking time zone (YYYY-MM-DD), stored literally.

start_time
string | null

Start time HH:MM in the booking time zone.

end_time
string | null

End time HH:MM in the booking time zone.

start
string <date-time> | null

Unambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.

end
string <date-time> | null

Unambiguous ISO 8601 instant of the end.

timezone
string

The booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.

location_preference
string

Whether the consultation is in the office or online.

Allowed values: "office" "online"
meeting_link
string | null

Online-meeting URL, when set.

client
object

The external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.

Fields of client
name
string | null
email
string | null
phone
string | null
description
string | null
gdpr_consent
boolean

Whether the client's data-handling consent is recorded.

gdpr_consent_at
string <date-time> | null

When the consent was recorded.

cancel_reason
string | null

Reason recorded on cancellation.

payment
object

Read-only payment state. The API never changes payment; booking fees are handled in Flowyer.

Fields of payment
status
string
Allowed values: "none" "awaiting" "partial" "paid" "refunded"
method
string | null
Allowed values: "card" "transfer"
amount
number | null
currency
string | null
invoice_number
string | null
confirmed_at
string <date-time> | null
cancelled_at
string <date-time> | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.

created_via_api
object | null

Present when an external agent created the booking through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get booking

GET /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.

Scope
bookings:read

Path parameters

idrequired
integer>= 1

Booking id.

Example: 4321

Responses

200

OK.

datarequired
object
Fields of data
id
integer
Example: 4321
booking_number
string

Human booking number, e.g. "#B-0001".

status
string

pending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.

Allowed values: "pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired"
source
string

Where the booking came from. An API-created booking is "manual".

Allowed values: "flowyer" "website" "embed_widget" "manual"
lawyer
object | null

The lawyer (user_id from colleagues.list) the appointment is with.

Fields of lawyer
user_id
integer
name
string | null
date
string <date> | null

Calendar date in the booking time zone (YYYY-MM-DD), stored literally.

start_time
string | null

Start time HH:MM in the booking time zone.

end_time
string | null

End time HH:MM in the booking time zone.

start
string <date-time> | null

Unambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.

end
string <date-time> | null

Unambiguous ISO 8601 instant of the end.

timezone
string

The booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.

location_preference
string

Whether the consultation is in the office or online.

Allowed values: "office" "online"
meeting_link
string | null

Online-meeting URL, when set.

client
object

The external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.

Fields of client
name
string | null
email
string | null
phone
string | null
description
string | null
gdpr_consent
boolean

Whether the client's data-handling consent is recorded.

gdpr_consent_at
string <date-time> | null

When the consent was recorded.

cancel_reason
string | null

Reason recorded on cancellation.

payment
object

Read-only payment state. The API never changes payment; booking fees are handled in Flowyer.

Fields of payment
status
string
Allowed values: "none" "awaiting" "partial" "paid" "refunded"
method
string | null
Allowed values: "card" "transfer"
amount
number | null
currency
string | null
invoice_number
string | null
confirmed_at
string <date-time> | null
cancelled_at
string <date-time> | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.

created_via_api
object | null

Present when an external agent created the booking through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
    }
  }
}

Create booking

POST /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.

Scope
bookings:create

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

user_idrequired
integer>= 1

Lawyer the appointment is with (user_id from colleagues.list; must be an active office colleague).

Example: 42
daterequired
string <date>

Appointment day (YYYY-MM-DD) in the booking time zone.

Example: "2026-03-02"
timerequired
string

Start time HH:MM (24h) in the booking time zone. The end time is time + the office slot length. Must be a free slot.

Pattern: ^([01]\d|2[0-3]):[0-5]\d$
Example: "09:00"
client_namerequired
stringat most 255 characters

The client's name. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$
Example: "Kovács Anna"
client_emailrequired
stringat most 255 characters

The client's e-mail: the recipient of the confirmation.

Example: "anna@example.com"
client_phone
string | nullat most 50 characters

The client's phone. Plain text.

Pattern: ^[^<>]*$
client_description
string | nullat most 2000 characters

Subject / notes from the client. Plain text.

Pattern: ^[^<>]*$
location_preference
string

In-office or online consultation. Default "office".

Allowed values: "office" "online"
Default: "office"
meeting_link
string | nullat most 500 characters

Online-meeting URL (http/https), for an online consultation.

gdpr_consent
boolean

Record the client's data-handling consent. Default true (office staff books on the client's behalf).

Default: true
auto_confirm
boolean

When 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.

Default: false
idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
id
integer
Example: 4321
booking_number
string

Human booking number, e.g. "#B-0001".

status
string

pending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.

Allowed values: "pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired"
source
string

Where the booking came from. An API-created booking is "manual".

Allowed values: "flowyer" "website" "embed_widget" "manual"
lawyer
object | null

The lawyer (user_id from colleagues.list) the appointment is with.

Fields of lawyer
user_id
integer
name
string | null
date
string <date> | null

Calendar date in the booking time zone (YYYY-MM-DD), stored literally.

start_time
string | null

Start time HH:MM in the booking time zone.

end_time
string | null

End time HH:MM in the booking time zone.

start
string <date-time> | null

Unambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.

end
string <date-time> | null

Unambiguous ISO 8601 instant of the end.

timezone
string

The booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.

location_preference
string

Whether the consultation is in the office or online.

Allowed values: "office" "online"
meeting_link
string | null

Online-meeting URL, when set.

client
object

The external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.

Fields of client
name
string | null
email
string | null
phone
string | null
description
string | null
gdpr_consent
boolean

Whether the client's data-handling consent is recorded.

gdpr_consent_at
string <date-time> | null

When the consent was recorded.

cancel_reason
string | null

Reason recorded on cancellation.

payment
object

Read-only payment state. The API never changes payment; booking fees are handled in Flowyer.

Fields of payment
status
string
Allowed values: "none" "awaiting" "partial" "paid" "refunded"
method
string | null
Allowed values: "card" "transfer"
amount
number | null
currency
string | null
invoice_number
string | null
confirmed_at
string <date-time> | null
cancelled_at
string <date-time> | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.

created_via_api
object | null

Present when an external agent created the booking through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: true
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.

datarequired
object
Fields of data
id
integer
Example: 4321
booking_number
string

Human booking number, e.g. "#B-0001".

status
string

pending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.

Allowed values: "pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired"
source
string

Where the booking came from. An API-created booking is "manual".

Allowed values: "flowyer" "website" "embed_widget" "manual"
lawyer
object | null

The lawyer (user_id from colleagues.list) the appointment is with.

Fields of lawyer
user_id
integer
name
string | null
date
string <date> | null

Calendar date in the booking time zone (YYYY-MM-DD), stored literally.

start_time
string | null

Start time HH:MM in the booking time zone.

end_time
string | null

End time HH:MM in the booking time zone.

start
string <date-time> | null

Unambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.

end
string <date-time> | null

Unambiguous ISO 8601 instant of the end.

timezone
string

The booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.

location_preference
string

Whether the consultation is in the office or online.

Allowed values: "office" "online"
meeting_link
string | null

Online-meeting URL, when set.

client
object

The external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.

Fields of client
name
string | null
email
string | null
phone
string | null
description
string | null
gdpr_consent
boolean

Whether the client's data-handling consent is recorded.

gdpr_consent_at
string <date-time> | null

When the consent was recorded.

cancel_reason
string | null

Reason recorded on cancellation.

payment
object

Read-only payment state. The API never changes payment; booking fees are handled in Flowyer.

Fields of payment
status
string
Allowed values: "none" "awaiting" "partial" "paid" "refunded"
method
string | null
Allowed values: "card" "transfer"
amount
number | null
currency
string | null
invoice_number
string | null
confirmed_at
string <date-time> | null
cancelled_at
string <date-time> | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.

created_via_api
object | null

Present when an external agent created the booking through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Update booking

PATCH /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.

Scope
bookings:update

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

date
string <date>

New day (YYYY-MM-DD) in the booking time zone. Send date AND time together to reschedule.

Example: "2026-03-05"
time
string

New start time HH:MM. The end time is recomputed from the office slot length. Send date AND time together.

Pattern: ^([01]\d|2[0-3]):[0-5]\d$
Example: "11:00"
client_phone
string | nullat most 50 characters

The client's phone. Plain text. "" clears it.

Pattern: ^[^<>]*$
client_description
string | nullat most 2000 characters

Subject / notes. Plain text. "" clears it.

Pattern: ^[^<>]*$
meeting_link
string | nullat most 500 characters

Online-meeting URL (http/https). "" clears it.

location_preference
string

In-office or online consultation.

Allowed values: "office" "online"

Responses

200

Updated.

datarequired
object
Fields of data
id
integer
Example: 4321
booking_number
string

Human booking number, e.g. "#B-0001".

status
string

pending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.

Allowed values: "pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired"
source
string

Where the booking came from. An API-created booking is "manual".

Allowed values: "flowyer" "website" "embed_widget" "manual"
lawyer
object | null

The lawyer (user_id from colleagues.list) the appointment is with.

Fields of lawyer
user_id
integer
name
string | null
date
string <date> | null

Calendar date in the booking time zone (YYYY-MM-DD), stored literally.

start_time
string | null

Start time HH:MM in the booking time zone.

end_time
string | null

End time HH:MM in the booking time zone.

start
string <date-time> | null

Unambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.

end
string <date-time> | null

Unambiguous ISO 8601 instant of the end.

timezone
string

The booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.

location_preference
string

Whether the consultation is in the office or online.

Allowed values: "office" "online"
meeting_link
string | null

Online-meeting URL, when set.

client
object

The external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.

Fields of client
name
string | null
email
string | null
phone
string | null
description
string | null
gdpr_consent
boolean

Whether the client's data-handling consent is recorded.

gdpr_consent_at
string <date-time> | null

When the consent was recorded.

cancel_reason
string | null

Reason recorded on cancellation.

payment
object

Read-only payment state. The API never changes payment; booking fees are handled in Flowyer.

Fields of payment
status
string
Allowed values: "none" "awaiting" "partial" "paid" "refunded"
method
string | null
Allowed values: "card" "transfer"
amount
number | null
currency
string | null
invoice_number
string | null
confirmed_at
string <date-time> | null
cancelled_at
string <date-time> | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.

created_via_api
object | null

Present when an external agent created the booking through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Confirm booking

POST /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.

Scope
bookings:send

Path parameters

idrequired
integer>= 1

Booking id.

Example: 4321

Request body application/json

expected_updated_at
string <date-time> | null

Optional 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_link
string | nullat most 500 characters

Online-meeting URL (http/https) to set on the booking before the confirmation e-mail goes out.

Responses

200

OK.

datarequired
object
Fields of data
id
integer
Example: 4321
booking_number
string

Human booking number, e.g. "#B-0001".

status
string

pending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.

Allowed values: "pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired"
source
string

Where the booking came from. An API-created booking is "manual".

Allowed values: "flowyer" "website" "embed_widget" "manual"
lawyer
object | null

The lawyer (user_id from colleagues.list) the appointment is with.

Fields of lawyer
user_id
integer
name
string | null
date
string <date> | null

Calendar date in the booking time zone (YYYY-MM-DD), stored literally.

start_time
string | null

Start time HH:MM in the booking time zone.

end_time
string | null

End time HH:MM in the booking time zone.

start
string <date-time> | null

Unambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.

end
string <date-time> | null

Unambiguous ISO 8601 instant of the end.

timezone
string

The booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.

location_preference
string

Whether the consultation is in the office or online.

Allowed values: "office" "online"
meeting_link
string | null

Online-meeting URL, when set.

client
object

The external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.

Fields of client
name
string | null
email
string | null
phone
string | null
description
string | null
gdpr_consent
boolean

Whether the client's data-handling consent is recorded.

gdpr_consent_at
string <date-time> | null

When the consent was recorded.

cancel_reason
string | null

Reason recorded on cancellation.

payment
object

Read-only payment state. The API never changes payment; booking fees are handled in Flowyer.

Fields of payment
status
string
Allowed values: "none" "awaiting" "partial" "paid" "refunded"
method
string | null
Allowed values: "card" "transfer"
amount
number | null
currency
string | null
invoice_number
string | null
confirmed_at
string <date-time> | null
cancelled_at
string <date-time> | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.

created_via_api
object | null

Present when an external agent created the booking through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
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.

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).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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"
    }
  }
}

Cancel booking

POST /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.

Scope
bookings:send

Path parameters

idrequired
integer>= 1

Booking id.

Example: 4321

Request body application/json

expected_updated_at
string <date-time> | null

Optional optimistic-lock token: the updated_at from your last read. If the booking changed since, the API answers 409.

reason
string | nullat most 255 characters

Reason recorded as cancel_reason and shown in the cancellation e-mail. Plain text; "<" and ">" are not allowed.

Pattern: ^[^<>]*$

Responses

200

OK.

datarequired
object
Fields of data
id
integer
Example: 4321
booking_number
string

Human booking number, e.g. "#B-0001".

status
string

pending = awaiting the lawyer's confirmation; confirmed = approved; cancelled/completed; awaiting_payment/expired belong to the pay-before-confirm flow.

Allowed values: "pending" "confirmed" "cancelled" "completed" "awaiting_payment" "expired"
source
string

Where the booking came from. An API-created booking is "manual".

Allowed values: "flowyer" "website" "embed_widget" "manual"
lawyer
object | null

The lawyer (user_id from colleagues.list) the appointment is with.

Fields of lawyer
user_id
integer
name
string | null
date
string <date> | null

Calendar date in the booking time zone (YYYY-MM-DD), stored literally.

start_time
string | null

Start time HH:MM in the booking time zone.

end_time
string | null

End time HH:MM in the booking time zone.

start
string <date-time> | null

Unambiguous ISO 8601 instant of the start, composed with the booking time zone. Use this to compare instants across zones.

end
string <date-time> | null

Unambiguous ISO 8601 instant of the end.

timezone
string

The booking time zone (booking_settings.timezone), e.g. "Europe/Budapest". It governs date, start_time and end_time.

location_preference
string

Whether the consultation is in the office or online.

Allowed values: "office" "online"
meeting_link
string | null

Online-meeting URL, when set.

client
object

The external client who booked: name, e-mail, phone and the subject/notes. Personal data — for office staff only.

Fields of client
name
string | null
email
string | null
phone
string | null
description
string | null
gdpr_consent
boolean

Whether the client's data-handling consent is recorded.

gdpr_consent_at
string <date-time> | null

When the consent was recorded.

cancel_reason
string | null

Reason recorded on cancellation.

payment
object

Read-only payment state. The API never changes payment; booking fees are handled in Flowyer.

Fields of payment
status
string
Allowed values: "none" "awaiting" "partial" "paid" "refunded"
method
string | null
Allowed values: "card" "transfer"
amount
number | null
currency
string | null
invoice_number
string | null
confirmed_at
string <date-time> | null
cancelled_at
string <date-time> | null
created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to bookings.update / bookings.confirm / bookings.cancel as expected_updated_at.

created_via_api
object | null

Present when an external agent created the booking through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
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.

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).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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"
    }
  }
}

Tickets

List tickets

GET /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.

Scope
tickets:read
Flowyer permission
ticketing:read

Query parameters

status
string

open_states (default) = new+open+pending (the web default view); "all" includes closed; or one exact status.

Allowed values: "new" "open" "pending" "closed" "open_states" "all"
Default: "open_states"
mailbox_id
integer>= 1

Only tickets from this mailbox. A mailbox the user may not see yields an empty list (never a leak).

Example: 7
assigned_to_user_id
integer>= 0

Only tickets assigned to this colleague (id from colleagues.list); 0 = only unassigned tickets. Omit for any assignee.

requester_email
stringat most 255 characters

Only tickets whose external requester email matches exactly (case-insensitive).

Example: "ugyfel@example.com"
q
stringat most 100 characters

Free-text search over subject, requester name/email and ticket number (case ignored).

Example: "számla"
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer

Ticket id (use with tickets.get / tickets.update).

Example: 4321
ticket_number
string | null

Human ticket number, e.g. FLW-000123.

subject
string | null
status
string
Allowed values: "new" "open" "pending" "closed"
priority
string
Allowed values: "low" "normal" "high"
mailbox
object | null

The mailbox the ticket arrived through (central registry).

Fields of mailbox
id
integer
email
string | null
display_name
string | null
requester
object

The external person who wrote in.

Fields of requester
email
string | null
name
string | null
assigned_to
object | null

The colleague the ticket is assigned to, or null when unassigned.

Fields of assigned_to
user_id
integer
name
string | null
matter
object | null

The matter linked to the ticket, or null.

Fields of matter
id
integer
case_number
string
client
object | null

The client linked to the ticket, or null.

Fields of client
id
integer
name
string
last_message_at
string <date-time> | null

Time of the most recent thread activity; the list is ordered by this, newest first.

created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to tickets.update as expected_updated_at.

created_via_api
object | null

Present when the ticket was created through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Get ticket

GET /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).

Scope
tickets:read
Flowyer permission
ticketing:read

Path parameters

idrequired
integer>= 1

Ticket id.

Example: 4321

Query parameters

messages_limit
integer | null>= 1<= 500

Return only the newest N messages of the thread (still presented oldest first). Omit for the whole thread.

Responses

200

OK.

datarequired
object
Fields of data
id
integer

Ticket id (use with tickets.get / tickets.update).

Example: 4321
ticket_number
string | null

Human ticket number, e.g. FLW-000123.

subject
string | null
status
string
Allowed values: "new" "open" "pending" "closed"
priority
string
Allowed values: "low" "normal" "high"
mailbox
object | null

The mailbox the ticket arrived through (central registry).

Fields of mailbox
id
integer
email
string | null
display_name
string | null
requester
object

The external person who wrote in.

Fields of requester
email
string | null
name
string | null
assigned_to
object | null

The colleague the ticket is assigned to, or null when unassigned.

Fields of assigned_to
user_id
integer
name
string | null
matter
object | null

The matter linked to the ticket, or null.

Fields of matter
id
integer
case_number
string
client
object | null

The client linked to the ticket, or null.

Fields of client
id
integer
name
string
last_message_at
string <date-time> | null

Time of the most recent thread activity; the list is ordered by this, newest first.

created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to tickets.update as expected_updated_at.

created_via_api
object | null

Present when the ticket was created through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
messages
object[]

The message thread, oldest first.

Fields of messages
id
integer
direction
string

in = from the requester, out = reply/auto-ack sent from the mailbox, note = internal.

Allowed values: "in" "out" "note"
from
object
Fields of from
email
string | null
name
string | null
to
object[]
Fields of to
email
string
name
string | null
cc
object[]
Fields of cc
email
string
name
string | null
sent_status
string | null

Delivery state of an outbound message; null for in/note.

Allowed values: "pending" "sent" "failed" "scheduled"
body_text
string | null

Message 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_attachments
boolean
author
object | null

The colleague who wrote an out/note message; null for an incoming message.

Fields of author
user_id
integer
name
string | null
created_at
string <date-time> | null
linked_matter
object | null

The matter linked to the ticket (same as matter).

Fields of linked_matter
id
integer
case_number
string
linked_client
object | null

The client linked to the ticket (same as client).

Fields of linked_client
id
integer
name
string
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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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"
    }
  }
}

Reply to ticket

POST /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.

Scope
tickets:reply
Flowyer permission
ticketing:read

Path parameters

idrequired
integer>= 1

Ticket id (must be visible via the mailbox ACL).

Example: 4321

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

body_htmlrequired
stringat least 1 characters

The 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.

Example: "<p>Köszönjük megkeresését, csatoltan küldjük a választ.</p>"
cc
string[]at most 10 items

Extra Cc recipients (at most 10). The requester and the mailbox are excluded automatically; an invalid address answers 422.

status
string

Ticket status to set after replying (default "pending" = waiting for the requester). Records a status-change event.

Allowed values: "new" "open" "pending" "closed"
Default: "pending"
append_signature
boolean

Append the key user's saved ticket signature to the OUTGOING mail only (never stored in the thread).

Default: false
idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
ticket_id
integer
Example: 4321
message_id
integer

Id of the created outbound ticket_messages row.

Example: 98765
direction
string

Always "out".

sent_status
string | null

"pending" at create time; the async send flips it to sent/failed later.

Allowed values: "pending" "sent" "failed" "scheduled"
status
string

The ticket status after the reply.

Allowed values: "new" "open" "pending" "closed"
created_at
string <date-time> | null
created_via_api
object | null

Present when the reply was created through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: true
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.

datarequired
object
Fields of data
ticket_id
integer
Example: 4321
message_id
integer

Id of the created outbound ticket_messages row.

Example: 98765
direction
string

Always "out".

sent_status
string | null

"pending" at create time; the async send flips it to sent/failed later.

Allowed values: "pending" "sent" "failed" "scheduled"
status
string

The ticket status after the reply.

Allowed values: "new" "open" "pending" "closed"
created_at
string <date-time> | null
created_via_api
object | null

Present when the reply was created through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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>"
}'

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
}

Change ticket status

PATCH /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.

Scope
tickets:update
Flowyer permission
ticketing:read

Path parameters

idrequired
integer>= 1

Id of the record to modify.

Request body application/json

expected_updated_at
string <date-time> | null

The 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.

statusrequired
string

The new status. "closed" stamps closed_at; any other status clears it.

Allowed values: "new" "open" "pending" "closed"
Example: "open"

Responses

200

Updated.

datarequired
object
Fields of data
id
integer

Ticket id (use with tickets.get / tickets.update).

Example: 4321
ticket_number
string | null

Human ticket number, e.g. FLW-000123.

subject
string | null
status
string
Allowed values: "new" "open" "pending" "closed"
priority
string
Allowed values: "low" "normal" "high"
mailbox
object | null

The mailbox the ticket arrived through (central registry).

Fields of mailbox
id
integer
email
string | null
display_name
string | null
requester
object

The external person who wrote in.

Fields of requester
email
string | null
name
string | null
assigned_to
object | null

The colleague the ticket is assigned to, or null when unassigned.

Fields of assigned_to
user_id
integer
name
string | null
matter
object | null

The matter linked to the ticket, or null.

Fields of matter
id
integer
case_number
string
client
object | null

The client linked to the ticket, or null.

Fields of client
id
integer
name
string
last_message_at
string <date-time> | null

Time of the most recent thread activity; the list is ordered by this, newest first.

created_at
string <date-time> | null
updated_at
string <date-time> | null

Pass this to tickets.update as expected_updated_at.

created_via_api
object | null

Present when the ticket was created through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
updated
boolean
Value: true
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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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"
}'

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
}

Portal

List client portals

GET /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.

Scope
portal:read
Flowyer permission
client-portal:read, cases:read

Query parameters

matter_id
integer>= 1

Only the portal of this matter (404 matter_not_visible when the matter is not visible). A matter has at most one portal.

Example: 1234
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer

Portal (thread) id - the handle for portal.messages.list and portal.reply.

Example: 57
matter
object

The matter (case) the portal belongs to.

Fields of matter
id
integer
case_number
string
client_participants
object[]

The external client-side participants the firm is talking to.

Fields of client_participants
name
string | null
kind
string

client = a principal, contact = a client contact, email = a legacy e-mail-only grant.

Allowed values: "client" "contact" "email"
participant_count
integer

Number of live participants (staff and client).

message_count
integer

Number of non-deleted messages in the thread.

last_message_at
string <date-time> | null

When the newest non-deleted message was posted, ISO 8601 in the office zone; null for an empty thread.

unread_for_me
integer | null

Unread messages for the acting user's own participant row; null when the acting user is not a participant of this portal.

created_at
string <date-time> | null
paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

List portal messages

GET /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.

Scope
portal:read
Flowyer permission
client-portal:read, cases:read

Path parameters

idrequired
integer>= 1

Portal id (must be accessible).

Example: 57

Query parameters

include_deleted
boolean

Include deleted messages (their body is blanked). Default false.

Default: false
limit
integer>= 1<= 100

Page size, 1-100. Default 25.

Default: 25
Example: 25
offset
integer>= 0

Number of records to skip. Use offset + limit of the previous page to get the next one; pagination.total is the full count.

Default: 0
Example: 0

Responses

200

OK.

datarequired
object[]
Fields of data
id
integer

portal_messages.id.

Example: 8012
author
object

Who wrote the message.

Fields of author
participant_id
integer | null
name
string | null
kind
string

staff = a colleague of the firm, client = the external client.

Allowed values: "staff" "client"
direction
string

from_firm = a staff author, from_client = the external client.

Allowed values: "from_firm" "from_client"
body
string

The message as plain text (<br> decoded to line breaks). Empty string for a deleted message.

attachment
object | null

Attachment metadata when the message has a document; downloading it is out of scope. Null when there is none.

Fields of attachment
portal_document_id
integer
file_name
string
is_deleted
boolean
deleted_at
string <date-time> | null
created_at
string <date-time> | null

When the message was posted, ISO 8601 in the office zone.

paginationrequired
object
Fields of pagination
limitrequired
integer
offsetrequired
integer
totalrequired
integer

Total 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.

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.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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_...'

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
  }
}

Reply in client portal

POST /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.

Scope
portal:reply
Flowyer permission
client-portal:read, cases:read

Path parameters

idrequired
integer>= 1

Portal id to reply in (must be accessible).

Example: 57

Header parameters

Idempotency-Key
stringat most 128 characters

Recommended 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.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$

Request body application/json

bodyrequired
stringat least 1 charactersat most 20000 characters

The reply, plain text. Angle brackets are accepted and escaped on store. At most 20000 characters.

Example: "Tisztelt Ügyfelünk! A beadványt ma benyújtottuk."
idempotency_key
stringat most 128 characters

Same role as the Idempotency-Key header, for clients that cannot send custom headers. Letters, digits, _, -, : or ., at most 128 characters.

Pattern: ^[A-Za-z0-9_\-:.]{1,128}$
Example: "b8f1c2e0-6f7a-4d2e-9c1b-2a3d4e5f6a7b"

Responses

201

Created.

datarequired
object
Fields of data
message
object

The posted reply.

Fields of message
id
integer
Example: 8012
author
object

The acting staff user who posted the reply.

Fields of author
participant_id
integer | null
name
string | null
kind
string

staff = a colleague of the firm, client = the external client.

Allowed values: "staff" "client"
direction
string

Always "from_firm" for a reply posted through this API.

Allowed values: "from_firm" "from_client"
body
string

The posted message as plain text.

created_at
string <date-time> | null
portal
object

The portal (thread) and its matter the reply was posted in.

Fields of portal
id
integer
matter
object
Fields of matter
id
integer
case_number
string
created_via_api
object | null

Provenance of the message created through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: true
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.

datarequired
object
Fields of data
message
object

The posted reply.

Fields of message
id
integer
Example: 8012
author
object

The acting staff user who posted the reply.

Fields of author
participant_id
integer | null
name
string | null
kind
string

staff = a colleague of the firm, client = the external client.

Allowed values: "staff" "client"
direction
string

Always "from_firm" for a reply posted through this API.

Allowed values: "from_firm" "from_client"
body
string

The posted message as plain text.

created_at
string <date-time> | null
portal
object

The portal (thread) and its matter the reply was posted in.

Fields of portal
id
integer
matter
object
Fields of matter
id
integer
case_number
string
created_via_api
object | null

Provenance of the message created through this API.

Fields of created_via_api
kind
string

"key": created with a static API key; "oauth": created by an app connected through OAuth.

Allowed values: "key" "oauth"
key_id
integer

Id of the API key (kind "key") or of the connected app (kind "oauth"); the two are numbered separately.

key_name
string

Name the user gave the API key, e.g. "Grok integration", or the connected app's name for kind "oauth", e.g. "Claude".

user_id
integer
user
string | null
at
string <date-time> | null
created
boolean
Value: false
idempotent_replay
boolean
Value: true
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.

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.

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).

413

The request body is too large (request_too_large).

422

Validation failed or unknown field; details.fields lists the field errors.

429

Rate limit exceeded; Retry-After says when to retry.

503

The office is temporarily unavailable (maintenance or setup in progress); 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."
}'

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
}