This reference documents the version 1 REST API surface exposed under /api/v1/:
/api/v1/app/...Use REST API first if you want the conceptual overview. Use this page when you need concrete routes, request shapes, and response patterns.
The REST API is exposed under the tenant host at:
/api/v1/
The router accepts these methods:
GETPOSTHEADPUTDELETEPATCHSEARCHOPTIONSAuthenticated endpoints accept the API key in either header:
keyx-api-keyThe acting identity is the User that owns the API key. Permissions are then evaluated through that user context.
The implementation uses three request styles:
/auth and /deauthmultipart/form-data with field name file for uploadsFor browser-based clients, the presenter also handles OPTIONS preflight requests and allows the headers Content-Type, Authorization, key, x-api-key, and x-custom-auth-headers.
Generic collection list endpoints return:
{
"stats": {
"page": 1,
"itemsPerPage": 25,
"fetched": 25,
"total": 143
},
"data": []
}
Generic collection single-item create, read, update, and patch operations return one resource object.
The API uses these status patterns consistently:
200 OK for successful reads and updates201 Created for successful creates204 No Content for empty result sets and deletes304 Not Modified for cached GET/SEARCH responses where the client's If-None-Match matches the current ETag400 Bad Request for malformed input or unsupported parameter combinations401 Unauthorized mainly for failed credential-based /auth requests403 Forbidden for missing permissions and, in practice, many missing or invalid API-key cases404 Not Found when a collection, item, or command target does not exist429 Too Many Requests when API rate limits are reached500 Internal Server Error for processing failures501 Not Implemented for reserved but unsupported routesRely on the HTTP status first. Some error responses also carry a JSON body with a message.
Successful GET and SEARCH responses on the generic collection API carry these headers:
| Header | Meaning |
|---|---|
ETag |
Strong validator over the response body, formatted as "<hex>". Always emitted when the response was eligible for caching, regardless of cache hit/miss. |
Cache-Control: private, must-revalidate |
Tells intermediate caches to keep the response per-user and to revalidate before reuse. |
Clients can opt into conditional requests by sending the previously received ETag back in If-None-Match. The server responds 304 Not Modified with an empty body when the value still matches.
To bypass the cache for a single request, send either:
Cache-Control: no-cache request header?_noCache=1 query parameterCache-eligible request shapes:
GET /api/v1/collection/{collection} (list)SEARCH /api/v1/collection/{collection} (list with body filters)GET /api/v1/collection/{collection}/{item} (get one)GET /api/v1/collection/{collection}/get-by/{property}/{item} (get one by property)Single-resource fetches are not cached for types that have read-access logging enabled — those reads must always reach the database so the audit log fires.
The cache is keyed per authenticated user and effective permission scope; entries are invalidated automatically when the underlying resource is created, updated, or deleted through the platform.
| Route | Purpose |
|---|---|
POST /api/v1/auth |
Create a new API key from user credentials |
GET /api/v1/auth |
Validate the API key sent in request headers |
POST /api/v1/deauth |
Revoke an API key |
GET|SEARCH /api/v1/collection/{collection} |
List resources |
GET /api/v1/collection/{collection}/{item} |
Get one resource by ID or UUID |
GET /api/v1/collection/{collection}/get-by/{property}/{item} |
Get the first matching resource by alternate property |
POST /api/v1/collection/{collection} |
Create a resource |
PUT|PATCH|DELETE /api/v1/collection/{collection}/{item} |
Replace, partially update, or delete a resource |
POST /api/v1/upload/{typeId}/{propertyId} |
Upload a file and receive promise IDs |
/api/v1/app/{module}.{action} |
Execute command-style module endpoints |
POST /api/v1/authCreates a new API key from user credentials.
Use this when an integration needs its own long-lived API key tied to a dedicated application user.
Request parameters are read as normal POST fields:
| Field | Required | Meaning |
|---|---|---|
user |
yes | User login name |
password |
yes | User password |
valid |
no | Expiration date, default 9999-12-31 |
description |
no | Key description |
Response shape:
{
"authToken": {
"id": 12,
"userId": 34,
"keyString": "...",
"validFrom": "...",
"validTo": "...",
"description": "Login via API",
"permissions": []
}
}
Notes:
401429GET /api/v1/authValidates the API key sent in the key or x-api-key header.
Response shape:
{
"status": "valid",
"validTo": "2026-12-31 00:00:00"
}
If the key is invalid, status is invalid and validTo is an empty string.
POST /api/v1/deauthRevokes an API key.
Request parameters are read as normal POST fields:
| Field | Required | Meaning |
|---|---|---|
key |
yes | API key string to revoke |
Successful response:
{
"message": "API key deleted."
}
The generic collection API works against a type. The {collection} placeholder accepts the type ID or type system name.
Where an {item} path segment is used, the implementation accepts a numeric ID and also resolves UUID values.
GET /api/v1/collection/{collection}
SEARCH /api/v1/collection/{collection}
Supported query parameters:
| Parameter | Meaning |
|---|---|
page |
Page number, defaults to 1 |
perPage |
Page size, capped by the tenant API max page length |
orderBy |
Sort property |
orderDir |
asc or desc |
propsMeta |
Include property metadata for selected fields |
showFields |
Explicit field allowlist |
hideFields |
Explicit field denylist |
wrapFields |
Wrap selected fields in metadata-rich output |
tags |
Tag filter, as array or comma-separated IDs |
tagsMode |
any or all |
includeTagDescendants |
Whether descendant tags are included |
filters |
Array of typed filter definitions |
Filter payloads follow the platform's standard filter definition model. At minimum, each filter object must identify:
propertytypeRecommended shape:
{
"filters": [
{
"property": "status",
"type": "in",
"param": ["open", "in_progress"]
}
]
}
For query-string usage, filters is expected as a JSON-encoded array. In practice, SEARCH with a JSON body is the cleaner option for complex filters.
Use SEARCH when you want a search-style list request with a request body. Do not combine SEARCH with:
/get-by/...{item} pathGET /api/v1/collection/{collection}/{item}
Returns one resource object.
GET /api/v1/collection/{collection}/get-by/{property}/{item}
Returns the first resource matching the property value. This route does not accept filter definitions.
POST /api/v1/collection/{collection}
Send a JSON body using property identifiers as keys.
Notes:
id and uuid are ignored on inputnull to a file property erases the current valueSuccessful create returns 201 Created and the created resource object.
PUT /api/v1/collection/{collection}/{item}
Sends a full replacement update. The API validates and saves the resource, then returns the resulting resource object.
PATCH /api/v1/collection/{collection}/{item}
Partial updates support normal field replacement and some additive operators:
field+= appends to strings, adds to numbers, or merges arraysfield+== adds only missing values to arraysFor multiple file properties, patch requests also support:
{
"attachments": {
"add": ["promise-id-1"],
"remove": ["existing-file-name.ext"]
}
}
DELETE /api/v1/collection/{collection}/{item}
Deletes the resource and returns 204 No Content.
The tags collection is implemented specially rather than through generic typed-object CRUD.
GET /api/v1/collection/tags
SEARCH /api/v1/collection/tags
Supported parameters:
| Parameter | Meaning |
|---|---|
page |
Page number |
perPage |
Page size |
search |
Text search |
parent |
Parent tag ID |
showFields |
Explicit field allowlist |
hideFields |
Explicit field denylist |
Any authenticated user can list tags. Users with manageTags receive broader visibility, including full tag information and private-tag access.
Response shape follows the generic list format with stats and data.
GET /api/v1/collection/tags/{item}
Returns one tag object. The tag identifier is resolved through the tag manager, so integrations should treat it as the platform tag identifier rather than only a numeric ID.
POST /api/v1/collection/tags
PUT|PATCH /api/v1/collection/tags/{item}
DELETE /api/v1/collection/tags/{item}
Write access requires manageTags or createPrivateTags.
If the caller has createPrivateTags but not manageTags, created tags are forced to private.
The notifications collection is also implemented specially.
GET /api/v1/collection/notifications
Supported query parameters:
| Parameter | Meaning |
|---|---|
unread_only |
Set to "true" to limit to unread notifications |
limit |
Maximum number of returned items, default 25 |
cursor |
Cursor for pagination |
after |
Return items after the given point |
recipient_custom_id |
Look up notifications for a custom recipient identifier |
Access requires either:
notificationsgetNotificationsPerCustomIdSuccessful response:
{
"items": [],
"nextCursor": null,
"hasMore": false
}
If the result set is empty, the endpoint returns 204.
GET /api/v1/collection/notifications/{item}
Returns one notification object.
PATCH /api/v1/collection/notifications/{item}
Supported patch operations:
{ "read": true } marks the notification as read and returns the updated notification{ "archive": true } archives the notification and returns { "message": "Notification archived." }Other notification write operations are rejected:
POST is not supportedPUT is not supportedDELETE is not supportedCommand endpoints live under:
/api/v1/app/{module}.{action}
backend.versionGET /api/v1/app/backend.version
Returns backend version information for users allowed to use the REST API.
Response shape:
{
"id": 123,
"code": "2026.04",
"notes": "...",
"release_time": "2026-04-01 10:00:00"
}
user.currentGET /api/v1/app/user.current
Returns the currently authenticated user as a processed resource object.
Requires the system permission profile.
user.registerGET /api/v1/app/user.register
POST /api/v1/app/user.register
Requires the system permission apiUserRegistration.
GET user.registerReturns the allowed registration envelope configured for the tenant:
{
"allowedDomains": [],
"allowedRoles": [],
"allowedGroups": []
}
POST user.registerCreates a user account.
Minimum required fields from the implementation:
| Field | Required | Meaning |
|---|---|---|
user_id |
yes | User e-mail address |
user_name |
yes | Display name |
Supported registration-related fields visible in the implementation:
rolesuser_grouppasswordverify_accountIf verify_account is provided, the API creates an account-verification challenge and returns its identifier.
Successful response shape:
{
"object": {},
"apiKey": null,
"challenge": false
}
Notes:
apiKey is returned only when the created user can use the API and the request included credentials needed for default key creationuser.challengePOST /api/v1/app/user.challenge
Processes an account-verification challenge.
This action does not apply a separate permission gate in the REST module. The challenge payload itself is the control point.
Request body:
{
"userId": 123,
"challengeId": "abc",
"method": "mail_link",
"secret": "..."
}
Possible responses:
{ "methodDone": true, "challengeCompleted": true }
{ "methodDone": true, "challengeCompleted": false }
{ "methodDone": false, "challengeCompleted": false }
{ "message": "Method already done." }
user.resetpasswordPOST /api/v1/app/user.resetpassword
Requires:
apiUserPasswordResetallowApiPasswordResetRequest body:
| Field | Required | Meaning |
|---|---|---|
user_id |
yes | E-mail of the user whose password is being reset. |
redirect_url |
no | Absolute URL the user should be forwarded to once the password-reset flow inside the platform completes. The platform appends status=success (on success) or status=error&reason=... (on failure) to it. The URL is encrypted with the tenant's security key and embedded in the e-mailed link so it cannot be tampered with. |
{
"user_id": "person@example.com",
"redirect_url": "https://app.example.com/after-reset"
}
Successful response:
{
"message": "Password reset initiated. E-mail sent to the user."
}
Validation errors:
400 — redirect_url is present but not a valid URL.tags.objectGET /api/v1/app/tags.object?type={type}&item={item}
Returns the tags currently assigned to one object.
Required query parameters:
| Parameter | Meaning |
|---|---|
type |
Type ID or system name |
item |
Object ID or UUID |
The caller must have show permission on the target type.
Successful response:
{
"typeId": 1000002,
"objectId": 15,
"tags": []
}
tags.assignPOST /api/v1/app/tags.assign
Assigns tags to an object.
The caller must have edit permission on the target type.
Request body:
{
"type": "project",
"item": 15,
"operation": "replace",
"tags": [12, 18]
}
Supported operations:
addremovereplaceSuccessful response:
{
"typeId": 1000002,
"objectId": 15,
"operation": "replace",
"tags": []
}
POST /api/v1/upload/{typeId}/{propertyId}Uploads one or more files for a file property and returns promise IDs.
Request requirements:
multipart/form-datafileExample response:
[
"promise-id-1",
"promise-id-2"
]
Those promise IDs are then used as field values in subsequent create or update requests for the target resource.
The router exposes:
/api/v1/collection/{collection}/{item}/relation/{relationType}/{relationRole}
The current implementation returns 501 Not Implemented.