OpenAPI JSONMarkdown Docs

OpenAPI Explorer

Auto-generated OpenAPI definition for all enabled modules.

Default server: https://trainmaxr.pboj.smallhost.pl/api

Authentication & Accounts

Showing 20 of 35 endpoints
GET/auth/admin/nav
Auth required

Resolve backend chrome bootstrap payload

Returns the backend chrome payload available to the authenticated administrator after applying scope, RBAC, role defaults, and personal sidebar preferences.

Responses

200Backend chrome payload
Content-Type: application/json
{
  "brand": null,
  "groups": [
    {
      "name": "string",
      "items": [
        {
          "href": "string",
          "title": "string"
        }
      ]
    }
  ],
  "settingsSections": [
    {
      "id": "string",
      "label": "string",
      "items": [
        {
          "id": "string",
          "label": "string",
          "href": "string"
        }
      ]
    }
  ],
  "settingsPathPrefixes": [
    "string"
  ],
  "profileSections": [
    {
      "id": "string",
      "label": "string",
      "items": [
        {
          "id": "string",
          "label": "string",
          "href": "string"
        }
      ]
    }
  ],
  "profilePathPrefixes": [
    "string"
  ],
  "grantedFeatures": [
    "string"
  ],
  "roles": [
    "string"
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/auth/admin/nav" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/auth/autologin

Auto sign-in using env-configured demo credentials

When OM_AUTOLOGIN_EMAIL / OM_AUTOLOGIN_PASSWORD are configured, signs the visitor in with those credentials and redirects into the app. Intended for single-tenant demo instances only. Falls back to the login page when disabled or misconfigured.

Responses

200Success response
Content-Type: application/json
"string"
307Redirect into the app (or to /login on failure)
Content-Type: text/html
string

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/auth/autologin" \
  -H "Accept: application/json"
POST/auth/feature-check
Auth required

Check feature grants for the current user

Evaluates which of the requested features are available to the signed-in user within the active tenant / organization context.

Request body (application/json)

{
  "features": [
    "string"
  ]
}

Responses

200Evaluation result
Content-Type: application/json
{
  "ok": true,
  "granted": [
    "string"
  ],
  "userId": "string"
}
400Invalid request — features array missing, too large, or contains invalid entries
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/auth/feature-check" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"features\": [
    \"string\"
  ]
}"
GET/auth/features
Auth requiredauth.acl.manage

List declared feature flags

Returns all static features contributed by the enabled modules along with their module source. Requires features: auth.acl.manage

Responses

200Aggregated feature catalog
Content-Type: application/json
{
  "items": [
    {
      "id": "string",
      "title": "string",
      "module": "string"
    }
  ],
  "modules": [
    {
      "id": "string",
      "title": "string"
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/auth/features" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/auth/locale

Set locale and redirect

Stores the selected locale in a cookie and redirects to a safe local path.

Parameters

NameInRequiredSchemaDescription
localequeryYesany—
redirectqueryNoany—

Responses

200Success response
Content-Type: application/json
"string"
302Locale cookie set and request redirected
Content-Type: application/json
"string"
400Invalid locale
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/auth/locale?locale=en" \
  -H "Accept: application/json"
POST/auth/locale

Set locale

Stores the selected locale in a cookie and returns a JSON success response.

Request body (application/json)

{
  "locale": "en"
}

Responses

200Locale cookie set
Content-Type: application/json
{
  "ok": true
}
400Invalid locale or malformed request body
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/auth/locale" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"locale\": \"en\"
}"
POST/auth/login

Authenticate user credentials

Validates the submitted credentials and issues a bearer token cookie for subsequent API calls.

Request body (application/x-www-form-urlencoded)

email=user%40example.com&password=string

Responses

200Authentication succeeded
Content-Type: application/json
{
  "ok": true,
  "token": "string",
  "redirect": null
}
400Validation failed
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Invalid credentials
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403User lacks required role
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
429Too many login attempts
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/auth/login" \
  -H "Accept: application/json" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "email=user%40example.com&password=string"
POST/auth/logout
Auth required

Invalidate session and redirect

Clears authentication cookies and redirects the browser to the login page.

Responses

201Success response
Content-Type: application/json
"string"
302Redirect to login after successful logout
Content-Type: text/html
string

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/auth/logout" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/auth/profile
Auth required

Get current profile

Returns the email address for the signed-in user.

Responses

200Profile payload
Content-Type: application/json
{
  "email": "user@example.com",
  "roles": [
    "string"
  ]
}
404User not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/auth/profile" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/auth/profile
Auth required

Update current profile

Updates the email address or password for the signed-in user.

Request body (application/json)

{}

Responses

200Profile updated
Content-Type: application/json
{
  "ok": true,
  "email": "user@example.com"
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/auth/profile" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
POST/auth/reset

Send reset email

Requests a password reset email for the given account. The endpoint always returns `ok: true` to avoid leaking account existence.

Request body (application/x-www-form-urlencoded)

email=user%40example.com

Responses

200Reset email dispatched (or ignored for unknown accounts)
Content-Type: application/json
{
  "ok": true
}
400Invalid request origin
Content-Type: application/json
{
  "error": "string"
}
429Too many password reset requests
Content-Type: application/json
{
  "error": "string"
}
500Password reset email origin is not configured
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/auth/reset" \
  -H "Accept: application/json" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "email=user%40example.com"
POST/auth/reset/confirm

Complete password reset

Validates the reset token and updates the user password.

Request body (application/x-www-form-urlencoded)

token=string&password=string

Responses

200Password reset succeeded
Content-Type: application/json
{
  "ok": true,
  "redirect": "string"
}
400Invalid token or payload
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
429Too many reset confirmation attempts
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/auth/reset/confirm" \
  -H "Accept: application/json" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=string&password=string"
GET/auth/roles
Auth requiredauth.roles.list

List roles

Returns available roles within the current tenant. Super administrators receive visibility across tenants. Requires features: auth.roles.list

Parameters

NameInRequiredSchemaDescription
idqueryNoany—
pagequeryNoany—
pageSizequeryNoany—
searchqueryNoany—
tenantIdqueryNoany—

Responses

200Role collection
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "usersCount": 1,
      "tenantId": null,
      "tenantName": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/auth/roles?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/auth/roles
Auth requiredauth.roles.manage

Create role

Creates a new role anchored to the caller's tenant. Non-superadmins cannot target another tenant; supplying a foreign `tenantId` is rejected. Requires features: auth.roles.manage

Request body (application/json)

{
  "name": "string"
}

Responses

201Role created
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/auth/roles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\"
}"
PUT/auth/roles
Auth requiredauth.roles.manage

Update role

Updates mutable fields on an existing role. Requires features: auth.roles.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Role updated
Content-Type: application/json
{
  "ok": true
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}
404Role not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/auth/roles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
DELETE/auth/roles
Auth requiredauth.roles.manage

Delete role

Deletes a role by identifier. Fails when users remain assigned. Requires features: auth.roles.manage

Parameters

NameInRequiredSchemaDescription
idqueryYesanyRole identifier

Responses

200Role deleted
Content-Type: application/json
{
  "ok": true
}
400Role cannot be deleted
Content-Type: application/json
{
  "error": "string"
}
404Role not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/auth/roles?id=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/auth/roles/acl
Auth requiredauth.acl.manage

Fetch role ACL

Returns the feature and organization assignments associated with a role within the current tenant. Requires features: auth.acl.manage

Parameters

NameInRequiredSchemaDescription
roleIdqueryYesany—
tenantIdqueryNoany—

Responses

200Role ACL entry
Content-Type: application/json
{
  "isSuperAdmin": true,
  "features": [
    "string"
  ],
  "organizations": null,
  "updatedAt": null
}
400Invalid role id
Content-Type: application/json
{
  "error": "string"
}
404Role not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/auth/roles/acl?roleId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/auth/roles/acl
Auth requiredauth.acl.manage

Update role ACL

Replaces the feature list, super admin flag, and optional organization assignments for a role. Requires features: auth.acl.manage

Request body (application/json)

{
  "roleId": "00000000-0000-4000-8000-000000000000",
  "organizations": null
}

Responses

200Role ACL updated
Content-Type: application/json
{
  "ok": true,
  "sanitized": true
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}
404Role not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/auth/roles/acl" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"roleId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizations\": null
}"
GET/auth/session/refresh

Refresh auth cookie from session token (browser)

Exchanges an existing `session_token` cookie for a fresh JWT auth cookie and redirects the browser.

Parameters

NameInRequiredSchemaDescription
redirectqueryNoanyAbsolute or relative URL to redirect after refresh

Responses

200Success response
Content-Type: application/json
"string"
302Redirect to target location when session is valid
Content-Type: text/html
string

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/auth/session/refresh" \
  -H "Accept: application/json"
POST/auth/session/refresh

Refresh access token (API/mobile)

Exchanges a refresh token for a new JWT access token. Pass the refresh token obtained from login in the request body.

Request body (application/json)

{
  "refreshToken": "string"
}

Responses

200New access token issued
Content-Type: application/json
{
  "ok": true,
  "accessToken": "string",
  "expiresIn": 1
}
400Missing refresh token
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Invalid or expired token
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
429Too many refresh attempts
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/auth/session/refresh" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"refreshToken\": \"string\"
}"

Directory (Tenants & Organizations)

Showing 2 of 2 endpoints
GET/directory/organizations/lookup

Public organization lookup by slug

Responses

200Success response
Content-Type: application/json
"string"

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/directory/organizations/lookup" \
  -H "Accept: application/json"
GET/directory/tenants/lookup

Public tenant lookup

Responses

200Success response
Content-Type: application/json
"string"

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/directory/tenants/lookup" \
  -H "Accept: application/json"

API Documentation

Showing 1 of 1 endpoints
GET/version

Deployed Open Mercato version

Responses

200Success response
Content-Type: application/json
"string"

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/version" \
  -H "Accept: application/json"

Audit & Action Logs

Showing 5 of 5 endpoints
GET/audit_logs/audit-logs/access
Auth requiredaudit_logs.view_self

Retrieve access logs

Fetches paginated access audit logs scoped to the authenticated user. Tenant administrators can optionally expand the search to other actors or organizations. Requires features: audit_logs.view_self

Parameters

NameInRequiredSchemaDescription
organizationIdqueryNoanyLimit results to a specific organization
actorUserIdqueryNoanyFilter by actor user id (tenant administrators only)
resourceKindqueryNoanyRestrict to a resource kind such as `order` or `product`
accessTypequeryNoanyAccess type filter, e.g. `read` or `export`
pagequeryNoanyPage number (default 1)
pageSizequeryNoanyPage size (default 50)
limitqueryNoanyExplicit maximum number of records when paginating manually
beforequeryNoanyReturn logs created before this ISO-8601 timestamp
afterqueryNoanyReturn logs created after this ISO-8601 timestamp

Responses

200Access logs returned successfully
Content-Type: application/json
{
  "items": [
    {
      "id": "string",
      "resourceKind": "string",
      "resourceId": "string",
      "accessType": "string",
      "actorUserId": null,
      "actorUserName": null,
      "tenantId": null,
      "tenantName": null,
      "organizationId": null,
      "organizationName": null,
      "fields": [
        "string"
      ],
      "context": null,
      "createdAt": "string"
    }
  ],
  "canViewTenant": true,
  "page": 1,
  "pageSize": 1,
  "total": 1,
  "totalPages": 1
}
400Invalid filters supplied
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/audit_logs/audit-logs/access" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/audit_logs/audit-logs/actions
Auth requiredaudit_logs.view_self

Fetch action logs

Returns recent action audit log entries. Tenant administrators can widen the scope to other actors or organizations, and callers can optionally restrict results to undoable actions. Requires features: audit_logs.view_self

Parameters

NameInRequiredSchemaDescription
organizationIdqueryNoanyLimit results to a specific organization
actorUserIdqueryNoanyFilter logs created by specific actor IDs (tenant administrators only). Accepts a single UUID or a comma-separated UUID list.
resourceKindqueryNoanyFilter by resource kind (e.g., "order", "product")
resourceIdqueryNoanyFilter by resource ID (UUID of the specific record)
actionTypequeryNoanyFilter by action type (`create`, `edit`, `delete`, `assign`). Accepts a single value or a comma-separated list.
fieldNamequeryNoanyFilter to entries where the given field changed. Accepts a single field name or a comma-separated list.
includeRelatedqueryNoanyWhen `true`, also returns changes to child entities linked via parentResourceKind/parentResourceId
includeTotalqueryNoanyWhen `true`, the response includes the filtered total count.
undoableOnlyqueryNoanyWhen `true`, only undoable actions are returned
limitqueryNoanyMaximum number of records to return (default 50, max 1000)
offsetqueryNoanyZero-based record offset for pagination (legacy — prefer page/pageSize)
pagequeryNoanyPage number (default 1)
pageSizequeryNoanyPage size (default 50, max 200)
sortFieldqueryNoanySort field: `createdAt`, `user`, `action`, `field`, or `source`.
sortDirqueryNoanySort direction: `asc` or `desc`.
beforequeryNoanyReturn actions created before this ISO-8601 timestamp
afterqueryNoanyReturn actions created after this ISO-8601 timestamp

Responses

200Action logs retrieved successfully
Content-Type: application/json
{
  "items": [
    {
      "id": "string",
      "commandId": "string",
      "actionLabel": null,
      "executionState": "done",
      "actorUserId": null,
      "actorUserName": null,
      "tenantId": null,
      "tenantName": null,
      "organizationId": null,
      "organizationName": null,
      "resourceKind": null,
      "resourceId": null,
      "parentResourceKind": null,
      "parentResourceId": null,
      "undoToken": null,
      "createdAt": "string",
      "updatedAt": "string",
      "snapshotBefore": null,
      "snapshotAfter": null,
      "changes": null,
      "context": null
    }
  ],
  "canViewTenant": true,
  "page": 1,
  "pageSize": 1,
  "total": 1,
  "totalPages": 1
}
400Invalid filter values
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/audit_logs/audit-logs/actions?includeRelated=false&includeTotal=false&undoableOnly=false" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/audit_logs/audit-logs/actions/export
Auth requiredaudit_logs.view_self

Export action logs as CSV

Returns a CSV attachment containing filtered action audit log entries. Tenant administrators can widen the scope to other actors or organizations. Requires features: audit_logs.view_self

Parameters

NameInRequiredSchemaDescription
organizationIdqueryNoanyLimit results to a specific organization
actorUserIdqueryNoanyFilter logs created by specific actor IDs (tenant administrators only). Accepts a single UUID or a comma-separated UUID list.
resourceKindqueryNoanyFilter by resource kind (e.g., "order", "product")
resourceIdqueryNoanyFilter by resource ID (UUID of the specific record)
actionTypequeryNoanyFilter by action type (`create`, `edit`, `delete`, `assign`). Accepts a single value or a comma-separated list.
fieldNamequeryNoanyFilter to entries where the given field changed. Accepts a single field name or a comma-separated list.
includeRelatedqueryNoanyWhen `true`, also returns changes to child entities linked via parentResourceKind/parentResourceId
undoableOnlyqueryNoanyWhen `true`, only undoable actions are returned
limitqueryNoanyMaximum number of records to export (default 1000, capped at 1000)
sortFieldqueryNoanySort field: `createdAt`, `user`, `action`, `field`, or `source`.
sortDirqueryNoanySort direction: `asc` or `desc`.
beforequeryNoanyReturn actions created before this ISO-8601 timestamp
afterqueryNoanyReturn actions created after this ISO-8601 timestamp

Responses

200CSV export generated successfully
Content-Type: application/json
{
  "file": "csv"
}
400Invalid filter values
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/audit_logs/audit-logs/actions/export?includeRelated=false&undoableOnly=false" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/audit_logs/audit-logs/actions/redo
Auth requiredaudit_logs.redo_self

Redo by action log id

Redoes the latest undone command owned by the caller. Requires the action to still be eligible for redo within tenant and organization scope. Requires features: audit_logs.redo_self

Request body (application/json)

{
  "logId": "string"
}

Responses

200Redo executed successfully
Content-Type: application/json
{
  "ok": true,
  "logId": null,
  "undoToken": null
}
400Log not eligible for redo
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/audit_logs/audit-logs/actions/redo" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"logId\": \"string\"
}"
POST/audit_logs/audit-logs/actions/undo
Auth requiredaudit_logs.undo_self

Undo action by token

Replays the undo handler registered for a command. The provided undo token must match the latest undoable log entry accessible to the caller. Requires features: audit_logs.undo_self

Request body (application/json)

{
  "undoToken": "string"
}

Responses

200Undo applied successfully
Content-Type: application/json
{
  "ok": true,
  "logId": "string"
}
400Invalid or unavailable undo token
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/audit_logs/audit-logs/actions/undo" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"undoToken\": \"string\"
}"

Notifications

Showing 13 of 13 endpoints
GET/notifications
Auth required

List notifications

Returns a paginated collection of notifications.

Parameters

NameInRequiredSchemaDescription
statusqueryNoany—
typequeryNoany—
severityqueryNoany—
sourceEntityTypequeryNoany—
sourceEntityIdqueryNoany—
sincequeryNoany—
pagequeryNoany—
pageSizequeryNoany—
idsqueryNoanyComma-separated list of record UUIDs to filter by (max 200).

Responses

200Paginated notifications
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "type": "string",
      "title": "string",
      "body": null,
      "titleKey": null,
      "bodyKey": null,
      "titleVariables": null,
      "bodyVariables": null,
      "icon": null,
      "severity": "string",
      "status": "string",
      "actions": [
        {
          "id": "string",
          "label": "string"
        }
      ],
      "sourceModule": null,
      "sourceEntityType": null,
      "sourceEntityId": null,
      "linkHref": null,
      "createdAt": "string",
      "readAt": null,
      "actionTaken": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/notifications?page=1&pageSize=20" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/notifications
Auth requirednotifications.create

Create notification

Creates a notification for a user. Requires features: notifications.create

Request body (application/json)

{
  "type": "string",
  "severity": "info",
  "recipientUserId": "00000000-0000-4000-8000-000000000000"
}

Responses

201Notification created
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/notifications" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"type\": \"string\",
  \"severity\": \"info\",
  \"recipientUserId\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/notifications/{id}/action
Auth required

POST /notifications/{id}/action

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

201Success response
Content-Type: application/json
"string"

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/notifications/:id/action" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/notifications/{id}/dismiss
Auth required

PUT /notifications/{id}/dismiss

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Success response
Content-Type: application/json
"string"

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/notifications/:id/dismiss" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/notifications/{id}/read
Auth required

PUT /notifications/{id}/read

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Success response
Content-Type: application/json
"string"

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/notifications/:id/read" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/notifications/{id}/restore
Auth required

PUT /notifications/{id}/restore

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Success response
Content-Type: application/json
"string"

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/notifications/:id/restore" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/notifications/batch
Auth requirednotifications.create

POST /notifications/batch

Requires features: notifications.create

Responses

201Success response
Content-Type: application/json
"string"

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/notifications/batch" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/notifications/feature
Auth requirednotifications.create

POST /notifications/feature

Requires features: notifications.create

Responses

201Success response
Content-Type: application/json
"string"

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/notifications/feature" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/notifications/mark-all-read
Auth required

PUT /notifications/mark-all-read

Responses

200Success response
Content-Type: application/json
"string"

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/notifications/mark-all-read" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/notifications/role
Auth requirednotifications.create

POST /notifications/role

Requires features: notifications.create

Responses

201Success response
Content-Type: application/json
"string"

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/notifications/role" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/notifications/settings
Auth requirednotifications.manage

GET /notifications/settings

Requires features: notifications.manage

Responses

200Success response
Content-Type: application/json
"string"

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/notifications/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/notifications/settings
Auth requirednotifications.manage

POST /notifications/settings

Requires features: notifications.manage

Responses

201Success response
Content-Type: application/json
"string"

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/notifications/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/notifications/unread-count
Auth required

GET /notifications/unread-count

Responses

200Success response
Content-Type: application/json
"string"

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/notifications/unread-count" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Events

Showing 2 of 2 endpoints
GET/events
Auth requiredworkflows.view

List declared events

Returns every declared event. Filters: category, module, excludeTriggerExcluded (default true). Requires features: workflows.view

Responses

200Declared events
Content-Type: application/json
{
  "data": [
    {
      "id": "string",
      "label": "string"
    }
  ],
  "total": 1
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/events" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/events/stream
Auth required

GET /events/stream

Responses

200Success response
Content-Type: application/json
"string"

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/events/stream" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Attachments

Showing 14 of 14 endpoints
GET/attachments
Auth requiredattachments.view

List attachments for a record

Returns uploaded attachments for the given entity record, ordered by newest first. Requires features: attachments.view

Parameters

NameInRequiredSchemaDescription
entityIdqueryYesanyEntity identifier that owns the attachments
recordIdqueryYesanyRecord identifier within the entity
pagequeryNoany—
pageSizequeryNoany—

Responses

200Attachments found for the record
Content-Type: application/json
{
  "items": [
    {
      "id": "string",
      "url": "string",
      "fileName": "string",
      "fileSize": 1,
      "createdAt": "string",
      "mimeType": null,
      "content": null
    }
  ]
}
400Missing entity or record identifiers
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/attachments?entityId=string&recordId=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/attachments
Auth requiredattachments.manage

Upload attachment

Uploads a new attachment using multipart form-data and stores metadata for later retrieval. Requires features: attachments.manage

Request body (multipart/form-data)

entityId=string
recordId=string
file=string

Responses

200Attachment stored successfully
Content-Type: application/json
{
  "ok": true,
  "item": {
    "id": "string",
    "url": "string",
    "fileName": "string",
    "fileSize": 1,
    "content": null
  }
}
400Payload validation error
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/attachments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: multipart/form-data" \
  -d "{
  \"entityId\": \"string\",
  \"recordId\": \"string\",
  \"file\": \"string\"
}"
DELETE/attachments
Auth requiredattachments.manage

Delete attachment

Removes an uploaded attachment and deletes the stored asset. Requires features: attachments.manage

Parameters

NameInRequiredSchemaDescription
idqueryYesany—

Responses

200Attachment deleted
Content-Type: application/json
{
  "ok": true
}
400Missing attachment identifier
Content-Type: application/json
{
  "error": "string"
}
404Attachment not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/attachments?id=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/attachments/file/{id}

Download or serve attachment file

Returns the raw file content for an attachment. Path parameter: {id} - Attachment UUID. Query parameter: ?download=1 - Force file download with Content-Disposition header. Access control is enforced based on partition settings.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200File content with appropriate MIME type
Content-Type: application/json
"string"
400Missing attachment ID
Content-Type: application/json
{
  "error": "string"
}
401Unauthorized - authentication required for private partitions
Content-Type: application/json
{
  "error": "string"
}
403Forbidden - insufficient permissions
Content-Type: application/json
{
  "error": "string"
}
404Attachment or file not found
Content-Type: application/json
{
  "error": "string"
}
500Partition misconfigured
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/attachments/file/:id" \
  -H "Accept: application/json"
GET/attachments/image/{id}/{slug}

Serve image with optional resizing

Returns an image attachment with optional on-the-fly resizing and cropping. Resized images are cached for performance. Only works with image MIME types. Path parameter: {id} - Attachment UUID. Query parameters: ?width=N (1-4000 pixels), ?height=N (1-4000 pixels), ?cropType=cover|contain (resize behavior).

Parameters

NameInRequiredSchemaDescription
idpathYesany—
slugpathNoany—

Responses

200Binary image content (Content-Type: image/jpeg, image/png, etc.)
Content-Type: application/json
"string"
400Invalid parameters, missing ID, or non-image attachment
Content-Type: application/json
{
  "error": "string"
}
401Unauthorized - authentication required for private partitions
Content-Type: application/json
{
  "error": "string"
}
403Forbidden - insufficient permissions
Content-Type: application/json
{
  "error": "string"
}
404Image not found
Content-Type: application/json
{
  "error": "string"
}
500Partition misconfigured or image rendering failed
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/attachments/image/:id/:slug" \
  -H "Accept: application/json"
GET/attachments/library
Auth requiredattachments.view

List attachments

Returns paginated list of attachments with optional filtering by search term, partition, and tags. Includes available tags and partitions. Requires features: attachments.view

Parameters

NameInRequiredSchemaDescription
pagequeryNoanyPage number for pagination
pageSizequeryNoanyNumber of items per page (max 100)
searchqueryNoanySearch by file name (case-insensitive)
partitionqueryNoanyFilter by partition code
tagsqueryNoanyFilter by tags (comma-separated)
sortFieldqueryNoanyField to sort by
sortDirqueryNoanySort direction

Responses

200Attachments list with pagination and metadata
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "fileName": "string",
      "fileSize": 1,
      "mimeType": "string",
      "partitionCode": "string",
      "partitionTitle": null,
      "url": null,
      "createdAt": "string",
      "tags": [
        "string"
      ],
      "assignments": [],
      "content": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1,
  "availableTags": [
    "string"
  ],
  "partitions": [
    {
      "code": "string",
      "title": "string",
      "description": null,
      "isPublic": true
    }
  ]
}
400Invalid query parameters
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/attachments/library?page=1&pageSize=25" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/attachments/library/{id}
Auth requiredattachments.view

Get attachment details

Returns complete details of an attachment including metadata, tags, assignments, and custom fields. Requires features: attachments.view

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Attachment details
Content-Type: application/json
{
  "item": {
    "id": "00000000-0000-4000-8000-000000000000",
    "fileName": "string",
    "fileSize": 1,
    "mimeType": "string",
    "partitionCode": "string",
    "partitionTitle": null,
    "tags": [
      "string"
    ],
    "assignments": [],
    "content": null,
    "customFields": null
  }
}
400Invalid attachment ID
Content-Type: application/json
{
  "error": "string"
}
404Attachment not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/attachments/library/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PATCH/attachments/library/{id}
Auth requiredattachments.manage

Update attachment metadata

Updates attachment tags, assignments, and custom fields. Emits CRUD side effects for indexing and events. Requires features: attachments.manage

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{}

Responses

200Attachment updated successfully
Content-Type: application/json
{
  "ok": true
}
400Invalid payload or attachment ID
Content-Type: application/json
{
  "error": "string"
}
404Attachment not found
Content-Type: application/json
{
  "error": "string"
}
500Failed to save attributes
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PATCH "https://trainmaxr.pboj.smallhost.pl/api/attachments/library/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
DELETE/attachments/library/{id}
Auth requiredattachments.manage

Delete attachment

Permanently deletes an attachment file from storage and database. Emits CRUD side effects. Requires features: attachments.manage

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Attachment deleted successfully
Content-Type: application/json
{
  "ok": true
}
400Invalid attachment ID
Content-Type: application/json
{
  "error": "string"
}
404Attachment not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/attachments/library/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/attachments/partitions
Auth requiredattachments.manage

List all attachment partitions

Returns all configured attachment partitions with storage settings, OCR configuration, and access control settings. Requires features: attachments.manage

Responses

200List of partitions
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "code": "string",
      "title": "string",
      "description": null,
      "isPublic": true,
      "requiresOcr": true,
      "ocrModel": null,
      "configJson": null,
      "createdAt": null,
      "updatedAt": null,
      "envKey": "string"
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/attachments/partitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/attachments/partitions
Auth requiredattachments.manage

Create new partition

Creates a new attachment partition with specified storage and OCR settings. Requires unique partition code. Requires features: attachments.manage

Request body (application/json)

{
  "code": "string",
  "title": "string",
  "description": null,
  "ocrModel": null,
  "storageDriver": "local",
  "configJson": null
}

Responses

201Partition created successfully
Content-Type: application/json
{
  "item": {
    "id": "00000000-0000-4000-8000-000000000000",
    "code": "string",
    "title": "string",
    "description": null,
    "isPublic": true,
    "requiresOcr": true,
    "ocrModel": null,
    "configJson": null,
    "createdAt": null,
    "updatedAt": null,
    "envKey": "string"
  }
}
400Invalid payload or partition code
Content-Type: application/json
{
  "error": "string"
}
409Partition code already exists
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/attachments/partitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"code\": \"string\",
  \"title\": \"string\",
  \"description\": null,
  \"ocrModel\": null,
  \"storageDriver\": \"local\",
  \"configJson\": null
}"
PUT/attachments/partitions
Auth requiredattachments.manage

Update partition

Updates an existing partition. Partition code cannot be changed. Title, description, OCR settings, and access control can be modified. Requires features: attachments.manage

Request body (application/json)

{
  "code": "string",
  "title": "string",
  "description": null,
  "ocrModel": null,
  "storageDriver": "local",
  "configJson": null,
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Partition updated successfully
Content-Type: application/json
{
  "item": {
    "id": "00000000-0000-4000-8000-000000000000",
    "code": "string",
    "title": "string",
    "description": null,
    "isPublic": true,
    "requiresOcr": true,
    "ocrModel": null,
    "configJson": null,
    "createdAt": null,
    "updatedAt": null,
    "envKey": "string"
  }
}
400Invalid payload or code change attempt
Content-Type: application/json
{
  "error": "string"
}
404Partition not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/attachments/partitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"code\": \"string\",
  \"title\": \"string\",
  \"description\": null,
  \"ocrModel\": null,
  \"storageDriver\": \"local\",
  \"configJson\": null,
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
DELETE/attachments/partitions
Auth requiredattachments.manage

Delete partition

Deletes a partition. Default partitions cannot be deleted. Partitions with existing attachments cannot be deleted. Requires features: attachments.manage

Responses

200Partition deleted successfully
Content-Type: application/json
{
  "ok": true
}
400Invalid ID or default partition deletion attempt
Content-Type: application/json
{
  "error": "string"
}
404Partition not found
Content-Type: application/json
{
  "error": "string"
}
409Partition in use
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/attachments/partitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/attachments/transfer
Auth requiredattachments.manage

Transfer attachments to different record

Transfers one or more attachments from one record to another within the same entity type. Updates attachment assignments and metadata to reflect the new record. Requires features: attachments.manage

Request body (application/json)

{
  "entityId": "string",
  "attachmentIds": [
    "00000000-0000-4000-8000-000000000000"
  ],
  "toRecordId": "string"
}

Responses

200Attachments transferred successfully
Content-Type: application/json
{
  "ok": true,
  "updated": 1
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}
404Attachments not found
Content-Type: application/json
{
  "error": "string"
}
500Attachment model missing
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/attachments/transfer" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"attachmentIds\": [
    \"00000000-0000-4000-8000-000000000000\"
  ],
  \"toRecordId\": \"string\"
}"

Customer Portal

Showing 19 of 19 endpoints
GET/customer_accounts/portal/events/stream

Subscribe to portal events via SSE (Portal Event Bridge)

Long-lived SSE connection that receives server-side events marked with portalBroadcast: true. Events are filtered by the customer's tenant, organization, and recipient user audience.

Responses

200Event stream (text/event-stream)
Content-Type: application/json
"string"
401Not authenticated
Content-Type: application/json
"string"

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/events/stream" \
  -H "Accept: application/json"
POST/customer_accounts/portal/feature-check

Check customer portal feature access

Checks which of the requested features the authenticated customer user has. Used by portal menu injection for feature-gating.

Request body (application/json)

{
  "features": [
    "string"
  ]
}

Responses

200Feature check result
Content-Type: application/json
{
  "ok": true,
  "granted": [
    "string"
  ]
}
400Invalid request
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/feature-check" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"features\": [
    \"string\"
  ]
}"
POST/customer_accounts/portal/logout

Customer logout

Revokes the current session and clears authentication cookies.

Responses

200Logged out
Content-Type: application/json
{
  "ok": true
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/logout" \
  -H "Accept: application/json"
GET/customer_accounts/portal/nav

Portal sidebar navigation

Returns the portal sidebar for the authenticated customer. Items are derived from each portal page's `nav` metadata and filtered by `requireCustomerFeatures` against the customer's grants (wildcards honored).

Responses

200Portal sidebar groups
Content-Type: application/json
{
  "ok": true,
  "orgSlug": "string",
  "groups": [
    {
      "id": "main",
      "items": [
        {
          "id": "string",
          "label": "string",
          "href": "string",
          "order": 1
        }
      ]
    }
  ],
  "grantedFeatures": [
    "string"
  ],
  "isPortalAdmin": true
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404Organization not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/nav" \
  -H "Accept: application/json"
GET/customer_accounts/portal/notifications

List customer notifications

Returns paginated notifications for the authenticated customer user. Dismissed notifications are excluded by default unless ?status=dismissed is specified.

Responses

200Notification list
Content-Type: application/json
{
  "ok": true,
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "type": "string",
      "title": "string",
      "body": null,
      "titleKey": null,
      "bodyKey": null,
      "titleVariables": null,
      "bodyVariables": null,
      "icon": null,
      "severity": "info",
      "status": "unread",
      "actions": [
        {
          "id": "string",
          "label": "string"
        }
      ],
      "sourceModule": null,
      "sourceEntityType": null,
      "sourceEntityId": null,
      "linkHref": null,
      "createdAt": "string",
      "readAt": null,
      "actionTaken": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/notifications" \
  -H "Accept: application/json"
PUT/customer_accounts/portal/notifications/{id}/dismiss

Dismiss notification

Dismisses a single notification for the authenticated customer user.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Notification dismissed
Content-Type: application/json
{
  "ok": true
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404Notification not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/notifications/00000000-0000-4000-8000-000000000000/dismiss" \
  -H "Accept: application/json"
PUT/customer_accounts/portal/notifications/{id}/read

Mark notification as read

Marks a single notification as read for the authenticated customer user.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Notification marked as read
Content-Type: application/json
{
  "ok": true
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404Notification not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/notifications/00000000-0000-4000-8000-000000000000/read" \
  -H "Accept: application/json"
PUT/customer_accounts/portal/notifications/mark-all-read

Mark all notifications as read

Marks all unread notifications as read for the authenticated customer user.

Responses

200All notifications marked as read
Content-Type: application/json
{
  "ok": true,
  "count": 1
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/notifications/mark-all-read" \
  -H "Accept: application/json"
GET/customer_accounts/portal/notifications/unread-count

Get unread notification count

Returns the number of unread notifications for the authenticated customer user.

Responses

200Unread count
Content-Type: application/json
{
  "ok": true,
  "unreadCount": 1
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/notifications/unread-count" \
  -H "Accept: application/json"
POST/customer_accounts/portal/password-change

Change customer password

Changes the authenticated customer user password after verifying the current password. Revokes all existing sessions.

Request body (application/json)

{
  "currentPassword": "string",
  "newPassword": "string"
}

Responses

200Password changed
Content-Type: application/json
{
  "ok": true
}
400Current password incorrect or validation failed
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/password-change" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"currentPassword\": \"string\",
  \"newPassword\": \"string\"
}"
GET/customer_accounts/portal/profile

Get customer profile

Returns the authenticated customer user profile with roles and permissions.

Responses

200Profile data
Content-Type: application/json
{
  "ok": true,
  "user": {
    "id": "00000000-0000-4000-8000-000000000000",
    "email": "string",
    "displayName": "string",
    "emailVerified": true,
    "customerEntityId": null,
    "personEntityId": null,
    "isActive": true,
    "lastLoginAt": null,
    "createdAt": "string"
  },
  "roles": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "slug": "string"
    }
  ],
  "resolvedFeatures": [
    "string"
  ],
  "isPortalAdmin": true
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/profile" \
  -H "Accept: application/json"
PUT/customer_accounts/portal/profile

Update customer profile

Updates the authenticated customer user profile.

Request body (application/json)

{}

Responses

200Profile updated
Content-Type: application/json
{
  "ok": true,
  "user": {
    "id": "00000000-0000-4000-8000-000000000000",
    "email": "string",
    "displayName": "string"
  }
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/profile" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{}"
GET/customer_accounts/portal/sessions

List customer sessions

Returns active sessions for the authenticated customer user.

Responses

200Session list
Content-Type: application/json
{
  "ok": true,
  "sessions": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "ipAddress": null,
      "userAgent": null,
      "lastUsedAt": null,
      "createdAt": "string",
      "expiresAt": "string",
      "isCurrent": true
    }
  ]
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/sessions" \
  -H "Accept: application/json"
POST/customer_accounts/portal/sessions-refresh

Refresh customer JWT from session token

Uses the session cookie to issue a fresh JWT access token.

Responses

200Token refreshed
Content-Type: application/json
{
  "ok": true,
  "resolvedFeatures": [
    "string"
  ]
}
401Invalid session
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/sessions-refresh" \
  -H "Accept: application/json"
DELETE/customer_accounts/portal/sessions/{id}

Revoke a customer session

Revokes a specific session (not the current one).

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Session revoked
Content-Type: application/json
{
  "ok": true
}
400Cannot revoke current session
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404Session not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/sessions/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json"
GET/customer_accounts/portal/users

List company portal users

Lists portal users associated with the same company. Paginated (default pageSize 25, max 100).

Parameters

NameInRequiredSchemaDescription
pagequeryNoany—
pageSizequeryNoany—

Responses

200Paginated user list
Content-Type: application/json
{
  "ok": true,
  "users": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "email": "string",
      "displayName": "string",
      "emailVerified": true,
      "isActive": true,
      "lastLoginAt": null,
      "createdAt": "string",
      "roles": [
        {
          "id": "00000000-0000-4000-8000-000000000000",
          "name": "string",
          "slug": "string"
        }
      ]
    }
  ],
  "total": 1,
  "totalPages": 1,
  "page": 1,
  "pageSize": 1
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/users" \
  -H "Accept: application/json"
POST/customer_accounts/portal/users-invite

Invite a user to the company portal

Creates an invitation for a new user to join the company portal.

Request body (application/json)

{
  "email": "user@example.com",
  "roleIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}

Responses

201Invitation created
Content-Type: application/json
{
  "ok": true,
  "invitation": {
    "id": "00000000-0000-4000-8000-000000000000",
    "email": "string",
    "expiresAt": "string"
  }
}
400Validation failed
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions or non-assignable role
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
429Too many invitation requests
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/users-invite" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"email\": \"user@example.com\",
  \"roleIds\": [
    \"00000000-0000-4000-8000-000000000000\"
  ]
}"
DELETE/customer_accounts/portal/users/{id}

Delete a company portal user

Soft deletes a portal user and revokes all their sessions.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200User deleted
Content-Type: application/json
{
  "ok": true
}
400Cannot delete self
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404User not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/users/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json"
PUT/customer_accounts/portal/users/{id}/roles

Update portal user roles

Assigns new roles to a company portal user.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "roleIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}

Responses

200Roles updated
Content-Type: application/json
{
  "ok": true
}
400Validation failed
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404User not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/portal/users/00000000-0000-4000-8000-000000000000/roles" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"roleIds\": [
    \"00000000-0000-4000-8000-000000000000\"
  ]
}"

Feature Toggles

Showing 12 of 12 endpoints
GET/feature_toggles/check/boolean
Auth required

Check if feature is enabled

Checks if a feature toggle is enabled for the current context.

Parameters

NameInRequiredSchemaDescription
identifierqueryYesanyFeature toggle identifier

Responses

200Feature status
Content-Type: application/json
{
  "enabled": true,
  "source": "override",
  "toggleId": "string",
  "identifier": "string",
  "tenantId": "string"
}
400Bad Request
Content-Type: application/json
{
  "error": "string"
}
404Tenant not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/check/boolean?identifier=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/feature_toggles/check/json
Auth required

Get json config

Gets the json configuration for a feature toggle.

Parameters

NameInRequiredSchemaDescription
identifierqueryYesanyFeature toggle identifier

Responses

200Json config
Content-Type: application/json
{
  "valueType": "json",
  "source": "override",
  "toggleId": "string",
  "identifier": "string",
  "tenantId": "string"
}
400Bad Request
Content-Type: application/json
{
  "error": "string"
}
404Tenant not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/check/json?identifier=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/feature_toggles/check/number
Auth required

Get number config

Gets the number configuration for a feature toggle.

Parameters

NameInRequiredSchemaDescription
identifierqueryYesanyFeature toggle identifier

Responses

200Number config
Content-Type: application/json
{
  "valueType": "number",
  "value": 1,
  "source": "override",
  "toggleId": "string",
  "identifier": "string",
  "tenantId": "string"
}
400Bad Request
Content-Type: application/json
{
  "error": "string"
}
404Tenant not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/check/number?identifier=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/feature_toggles/check/string
Auth required

Get string config

Gets the string configuration for a feature toggle.

Parameters

NameInRequiredSchemaDescription
identifierqueryYesanyFeature toggle identifier

Responses

200String config
Content-Type: application/json
{
  "valueType": "string",
  "value": "string",
  "source": "override",
  "toggleId": "string",
  "identifier": "string",
  "tenantId": "string"
}
400Bad Request
Content-Type: application/json
{
  "error": "string"
}
404Tenant not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/check/string?identifier=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/feature_toggles/global
Auth requiredfeature_toggles.view

List global feature toggles

Returns all global feature toggles with filtering and pagination. Requires superadmin role. Requires features: feature_toggles.view

Parameters

NameInRequiredSchemaDescription
pagequeryNoanyPage number for pagination
pageSizequeryNoanyNumber of items per page (max 200)
searchqueryNoanyCase-insensitive search across identifier, name, description, and category
typequeryNoanyFilter by toggle type (boolean, string, number, json)
categoryqueryNoanyFilter by category (case-insensitive partial match)
namequeryNoanyFilter by name (case-insensitive partial match)
identifierqueryNoanyFilter by identifier (case-insensitive partial match)
sortFieldqueryNoanyField to sort by
sortDirqueryNoanySort direction (ascending or descending)

Responses

200Feature toggles collection
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "identifier": "string",
      "name": "string",
      "description": null,
      "category": null,
      "type": "boolean",
      "defaultValue": null,
      "createdAt": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
400Invalid query parameters
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/global?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/feature_toggles/global
Auth requiredfeature_toggles.global.manage

Create global feature toggle

Creates a new global feature toggle. Requires superadmin role. Requires features: feature_toggles.global.manage

Request body (application/json)

{
  "identifier": "string",
  "name": "string",
  "description": null,
  "category": null,
  "type": "boolean",
  "defaultValue": null
}

Responses

201Feature toggle created
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/global" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"identifier\": \"string\",
  \"name\": \"string\",
  \"description\": null,
  \"category\": null,
  \"type\": \"boolean\",
  \"defaultValue\": null
}"
PUT/feature_toggles/global
Auth requiredfeature_toggles.global.manage

Update global feature toggle

Updates an existing global feature toggle. Requires superadmin role. Requires features: feature_toggles.global.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000",
  "description": null,
  "category": null,
  "defaultValue": null
}

Responses

200Feature toggle updated
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}
404Feature toggle not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/global" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"description\": null,
  \"category\": null,
  \"defaultValue\": null
}"
DELETE/feature_toggles/global
Auth requiredfeature_toggles.global.manage

Delete global feature toggle

Soft deletes a global feature toggle by ID. Requires superadmin role. Requires features: feature_toggles.global.manage

Parameters

NameInRequiredSchemaDescription
idqueryYesanyFeature toggle identifier

Responses

200Feature toggle deleted
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
400Invalid identifier
Content-Type: application/json
{
  "error": "string"
}
404Feature toggle not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/global?id=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/feature_toggles/global/{id}
Auth requiredfeature_toggles.view

Fetch feature toggle by ID

Returns complete details of a feature toggle. Requires features: feature_toggles.view

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Feature toggle detail
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "identifier": "string",
  "name": "string",
  "description": null,
  "category": null,
  "type": "boolean",
  "defaultValue": null,
  "createdAt": null,
  "updatedAt": null
}
400Invalid identifier
Content-Type: application/json
{
  "error": "string"
}
404Feature toggle not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/global/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/feature_toggles/global/{id}/override
Auth requiredfeature_toggles.view

Fetch feature toggle override

Returns feature toggle override. Requires features: feature_toggles.view

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Feature toggle overrides
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "tenantName": "string",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "toggleType": "boolean",
  "updatedAt": null
}
400Invalid request
Content-Type: application/json
{
  "error": "string"
}
404Feature toggle not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/global/:id/override" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/feature_toggles/overrides
Auth requiredfeature_toggles.view

List overrides

Returns list of feature toggle overrides. Requires features: feature_toggles.view

Parameters

NameInRequiredSchemaDescription
categoryqueryNoany—
namequeryNoany—
identifierqueryNoany—
sortFieldqueryNoany—
sortDirqueryNoany—
pagequeryNoany—
pageSizequeryNoany—

Responses

200List of overrides
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "toggleId": "00000000-0000-4000-8000-000000000000",
      "tenantName": "string",
      "tenantId": "00000000-0000-4000-8000-000000000000",
      "identifier": "string",
      "name": "string",
      "category": "string",
      "isOverride": true
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1,
  "isSuperAdmin": true
}
400Invalid query parameters
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/overrides?page=1&pageSize=25" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/feature_toggles/overrides
Auth requiredfeature_toggles.manage

Change override state

Enable, disable or inherit a feature toggle for a specific tenant. Requires features: feature_toggles.manage

Request body (application/json)

{
  "toggleId": "00000000-0000-4000-8000-000000000000",
  "isOverride": true
}

Responses

200Override updated
Content-Type: application/json
{
  "ok": true,
  "overrideToggleId": null
}
400Validation failed
Content-Type: application/json
{
  "error": "string"
}
404Not found
Content-Type: application/json
{
  "error": "string"
}
500Internal server error
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/feature_toggles/overrides" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"toggleId\": \"00000000-0000-4000-8000-000000000000\",
  \"isOverride\": true
}"

Clubs

Showing 10 of 10 endpoints
GET/clubs/facilities
Auth requiredclubs.facility.view

List facilities

Lists the club's pitches and locker rooms (tenant-scoped, retired entries included unless filtered). Requires features: clubs.facility.view

Parameters

NameInRequiredSchemaDescription
idqueryNoany—
pagequeryNoany—
pageSizequeryNoany—
facilityTypequeryNoany—
isActivequeryNoany—

Responses

200Facilities
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "facilityType": "pitch",
      "isActive": true,
      "createdAt": "2025-01-01T00:00:00.000Z",
      "updatedAt": null
    }
  ],
  "total": 1
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/clubs/facilities?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/clubs/facilities
Auth requiredclubs.facility.manage

Add a facility

Records a pitch or locker room in the club-wide inventory. Requires features: clubs.facility.manage

Request body (application/json)

{
  "name": "string",
  "facilityType": "pitch"
}

Responses

201Facility created
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "facilityType": "pitch",
  "isActive": true,
  "createdAt": "2025-01-01T00:00:00.000Z",
  "updatedAt": null
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/clubs/facilities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"facilityType\": \"pitch\"
}"
PUT/clubs/facilities
Auth requiredclubs.facility.manage

Edit a facility

Renames, retypes, or retires/reactivates a facility (isActive toggle). Requires features: clubs.facility.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Facility updated
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "facilityType": "pitch",
  "isActive": true,
  "createdAt": "2025-01-01T00:00:00.000Z",
  "updatedAt": null
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}
404Not found in the club
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/clubs/facilities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
DELETE/clubs/facilities
Auth requiredclubs.facility.manage

Soft-delete a facility

Soft-deletes a facility; historical references keep resolving. Requires features: clubs.facility.manage

Parameters

NameInRequiredSchemaDescription
idqueryYesany—

Responses

200Facility soft-deleted
Content-Type: application/json
{
  "ok": true
}
404Not found in the club
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/clubs/facilities?id=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/clubs/facilities/bulk-setup
Auth requiredclubs.facility.manage

Quick-create facilities from counts

Tops the club inventory up toward the requested pitch / locker room totals, creating named active facilities (never removes any). Requires features: clubs.facility.manage

Request body (application/json)

{
  "pitches": 1,
  "lockerRooms": 1
}

Responses

200Created facilities
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "facilityType": "pitch",
      "isActive": true,
      "createdAt": "2025-01-01T00:00:00.000Z",
      "updatedAt": null
    }
  ]
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/clubs/facilities/bulk-setup" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"pitches\": 1,
  \"lockerRooms\": 1
}"
GET/clubs/operator-clubs
Auth requiredclubs.operator.view

List clubs

Operator view of provisioned clubs (tenants). Requires features: clubs.operator.view

Parameters

NameInRequiredSchemaDescription
pagequeryNoany—
pageSizequeryNoany—
searchqueryNoany—

Responses

200Clubs
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "isActive": true,
      "createdAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/clubs/operator-clubs?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PATCH/clubs/operator-clubs/{id}
Auth requiredclubs.suspend

Suspend or restore a club

Toggles a club (tenant) active state. Superadmin-only (clubs.suspend). Requires features: clubs.suspend

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "isActive": true
}

Responses

200Updated club
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "isActive": true,
  "createdAt": null
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}
404Club not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PATCH "https://trainmaxr.pboj.smallhost.pl/api/clubs/operator-clubs/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"isActive\": true
}"
POST/clubs/provision
Auth requiredclubs.provision

Provision a club

Creates a tenant with its club-level Organization (org slug = club handle, keys the customer portal URL), seeds the club_admin role, default settings, and the player/guardian portal customer roles, and emails the first admin an invitation. Superadmin-only. Requires features: clubs.provision

Request body (application/json)

{
  "clubName": "string",
  "adminEmail": "user@example.com"
}

Responses

201Club provisioned
Content-Type: application/json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "organizationSlug": "string",
  "adminUserId": "00000000-0000-4000-8000-000000000000",
  "adminEmail": "user@example.com",
  "inviteEmailSent": true
}
400Invalid payload or provisioning failure
Content-Type: application/json
{
  "error": "string"
}
409Club handle (org slug) already taken by another organization
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/clubs/provision" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"clubName\": \"string\",
  \"adminEmail\": \"user@example.com\"
}"
GET/clubs/settings
Auth requiredclubs.settings.view

Get club settings

Returns the settings for the authenticated user's club (tenant), merged over defaults. Requires features: clubs.settings.view

Responses

200Club settings
Content-Type: application/json
{
  "logoUrl": "",
  "logotypeUrl": "",
  "handle": "",
  "handleMode": "random",
  "subdomainEnabled": true,
  "legalAge": 1,
  "seasonStart": null,
  "seasonEnd": null,
  "consentRequiredMinorSurvey": true,
  "reminderLeadDays": 1,
  "weeklyReportDay": "mon",
  "weeklyReportTime": "string",
  "healthCheckExpiryWarningDays": 1,
  "archivedRetentionYears": null
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/clubs/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/clubs/settings
Auth requiredclubs.settings.manage

Update club settings

Patches the club settings for the authenticated user's club. Requires features: clubs.settings.manage

Request body (application/json)

{
  "seasonStart": null,
  "seasonEnd": null,
  "archivedRetentionYears": null
}

Responses

200Updated club settings
Content-Type: application/json
{
  "logoUrl": "",
  "logotypeUrl": "",
  "handle": "",
  "handleMode": "random",
  "subdomainEnabled": true,
  "legalAge": 1,
  "seasonStart": null,
  "seasonEnd": null,
  "consentRequiredMinorSurvey": true,
  "reminderLeadDays": 1,
  "weeklyReportDay": "mon",
  "weeklyReportTime": "string",
  "healthCheckExpiryWarningDays": 1,
  "archivedRetentionYears": null
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/clubs/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"seasonStart\": null,
  \"seasonEnd\": null,
  \"archivedRetentionYears\": null
}"

Teams

Showing 7 of 7 endpoints
GET/teams/staff
Auth requiredteams.team.view

List staff assignments

Lists coach/coordinator assignments over the caller's visible teams (optionally one team). Requires features: teams.team.view

Responses

200Assignments
Content-Type: application/json
{
  "items": [
    {
      "id": "string",
      "userId": "string",
      "teamOrgId": "string",
      "role": "string",
      "userEmail": null,
      "userName": null
    }
  ]
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/teams/staff" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/teams/staff
Auth requiredteams.staff.assign

Assign an existing club member to teams

Admin: any role on any club team. Coordinator: only an existing club coach onto an overseen team. Requires features: teams.staff.assign

Request body (application/json)

{
  "userId": "00000000-0000-4000-8000-000000000000",
  "assignments": [
    {
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "role": "coach"
    }
  ]
}

Responses

200Assigned
Content-Type: application/json
{
  "ok": true
}
404Out of scope
Content-Type: application/json
{
  "error": "string"
}
409Already assigned
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/teams/staff" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"userId\": \"00000000-0000-4000-8000-000000000000\",
  \"assignments\": [
    {
      \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\",
      \"role\": \"coach\"
    }
  ]
}"
DELETE/teams/staff
Auth requiredteams.team.manage

Remove a member from a team

Ends team access for that team; club membership and identity persist. Requires features: teams.team.manage

Request body (application/json)

{
  "userId": "00000000-0000-4000-8000-000000000000",
  "teamOrgId": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed
Content-Type: application/json
{
  "ok": true
}
404Assignment not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/teams/staff" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"userId\": \"00000000-0000-4000-8000-000000000000\",
  \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/teams/staff/invite
Auth requiredteams.invite.staff

Invite staff

Club admin only: invites a net-new email into the club with team assignments. The invitee sets their own password from the emailed link. Requires features: teams.invite.staff

Request body (application/json)

{
  "email": "user@example.com",
  "name": null,
  "assignments": [
    {
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "role": "coach"
    }
  ]
}

Responses

201Invited
Content-Type: application/json
{
  "userId": "string",
  "inviteEmailSent": true
}
409Already a club member
Content-Type: application/json
{
  "error": "string"
}
422Invalid payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/teams/staff/invite" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"email\": \"user@example.com\",
  \"name\": null,
  \"assignments\": [
    {
      \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\",
      \"role\": \"coach\"
    }
  ]
}"
GET/teams/teams
Auth requiredteams.team.view

List teams

Lists the caller's visible teams (per-team scope; admins see the whole club). Archived teams are excluded unless includeArchived=true. Requires features: teams.team.view

Responses

200Teams
Content-Type: application/json
{
  "items": [
    {
      "id": "string",
      "name": "string",
      "slug": null,
      "sport": "string",
      "ageGroup": "string",
      "parentId": null,
      "logoUrl": null,
      "isActive": true
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/teams/teams" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/teams/teams
Auth requiredteams.team.manage

Create a team

Creates a team (directory org + profile) in the caller club. Club admin only. Requires features: teams.team.manage

Request body (application/json)

{
  "name": "string",
  "sport": "string",
  "ageGroup": "string",
  "parentId": null,
  "logoUrl": ""
}

Responses

201Created team
Content-Type: application/json
{
  "team": {
    "id": "string",
    "name": "string",
    "slug": null,
    "sport": "string",
    "ageGroup": "string",
    "parentId": null,
    "logoUrl": null,
    "isActive": true
  }
}
422Invalid payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/teams/teams" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"sport\": \"string\",
  \"ageGroup\": \"string\",
  \"parentId\": null,
  \"logoUrl\": \"\"
}"
PUT/teams/teams
Auth requiredteams.team.manage

Update a team

Renames, re-attributes, sets/clears the logo override, re-groups, or archives/reactivates a team. Club admin only. Requires features: teams.team.manage

Request body (application/json)

{
  "parentId": null,
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Updated team
Content-Type: application/json
{
  "team": {
    "id": "string",
    "name": "string",
    "slug": null,
    "sport": "string",
    "ageGroup": "string",
    "parentId": null,
    "logoUrl": null,
    "isActive": true
  }
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/teams/teams" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"parentId\": null,
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"

Players

Showing 6 of 6 endpoints
GET/players
Auth requiredplayers.player.view

List player profiles or fetch one by id

Team-scoped for staff, club-wide for the org-less club admin. Identity fields are decrypted per request; minor status is derived from the club legal age. Requires features: players.player.view

Responses

200Players page (or `{ item }` when `id` is passed)
Content-Type: application/json
{}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Player not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/players" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/players
Auth requiredplayers.player.manage

Create a player profile

Creates an active profile on a team in the caller scope, or team-less for the club admin. Requires features: players.player.manage

Request body (application/json)

{
  "firstName": "string",
  "lastName": "string",
  "dateOfBirth": "string",
  "homeTeamOrgId": null
}

Responses

201Created player id
Content-Type: application/json
{
  "id": null
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/players" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"firstName\": \"string\",
  \"lastName\": \"string\",
  \"dateOfBirth\": \"string\",
  \"homeTeamOrgId\": null
}"
PUT/players
Auth requiredplayers.player.manage

Update a player profile

Edits identity/sport attributes or repoints the home team within the caller scope. Requires features: players.player.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000",
  "homeTeamOrgId": null
}

Responses

200Updated player id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Player not found
Content-Type: application/json
{
  "error": "string"
}
409Archived record is locked
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/players" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"homeTeamOrgId\": null
}"
DELETE/players
Auth requiredplayers.player.delete

Delete a player (explicit request only)

Hard-deletes whatever remains of the record. Club-admin capability, distinct from archival. Requires features: players.player.delete

Responses

200Deleted
Content-Type: application/json
{
  "ok": true
}
404Player not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/players" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/players/{id}/archive
Auth requiredplayers.player.archive

Anonymize and archive a player

Strips personal identifiers and hash siblings in place, keeps the de-identified row reviewable, ends the home-team pointer, and emits players.player.archived. Idempotent; club-admin capability; NOT a delete. Requires features: players.player.archive

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Archived (or already archived)
Content-Type: application/json
{
  "ok": true,
  "alreadyArchived": true
}
404Player not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/players/:id/archive" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/players/unassigned
Auth requiredplayers.player.view_unassigned

List club players on no team

Every club player with no live home team - active by default, or archived to review departed, de-identified records. Admin + coordinator capability; tenant-scoped, never cross-club. Requires features: players.player.view_unassigned

Responses

200Unassigned players page
Content-Type: application/json
{
  "total": 1
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/players/unassigned" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Rosters

Showing 10 of 10 endpoints
GET/rosters
Auth requiredrosters.roster.view

List a team's roster

Live (active + transitioning) memberships joined with the decrypted player identity; includeEnded=true adds the retained history rows. Team-scoped: out-of-scope teams read as 404. Requires features: rosters.roster.view

Responses

200Roster rows
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "playerId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "status": "active",
      "joinedAt": "string",
      "leftAt": null,
      "shirtNumber": null,
      "position": null,
      "transferId": null,
      "firstName": null,
      "lastName": null
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/rosters" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/rosters
Auth requiredrosters.roster.manage

Add an existing club player to a roster

Creates an active membership with optional shirt number and position. A duplicate shirt number warns but saves. Requires features: rosters.roster.manage

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "teamOrgId": "00000000-0000-4000-8000-000000000000"
}

Responses

201Created membership id with soft warnings
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "warnings": [
    "string"
  ]
}
404Team or player not found
Content-Type: application/json
{
  "error": "string"
}
409Already on this roster
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/rosters" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\"
}"
PUT/rosters
Auth requiredrosters.roster.manage

Edit the roster-level attributes of a membership

Shirt number and position only — identity edits are a player-profile action and are rejected. Requires features: rosters.roster.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000",
  "shirtNumber": null,
  "position": null
}

Responses

200Updated membership id with soft warnings
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "warnings": [
    "string"
  ]
}
404Membership not found
Content-Type: application/json
{
  "error": "string"
}
409Membership already ended
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/rosters" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"shirtNumber\": null,
  \"position\": null
}"
DELETE/rosters
Auth requiredrosters.roster.manage

Remove a player from a roster

Ends the membership (status=ended + left date) and keeps the row as history; the player profile persists, and losing the last live membership leaves the player unassigned. Requires features: rosters.roster.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed
Content-Type: application/json
{
  "ok": true
}
404Membership not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/rosters" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/rosters/add-player
Auth requiredrosters.roster.manage

Create a player profile and its first membership in one step

The common coach action: profile + active membership in one transaction. A hash-based duplicate match and a taken shirt number return soft warnings and never block. Requires features: rosters.roster.manage

Request body (application/json)

{
  "firstName": "string",
  "lastName": "string",
  "dateOfBirth": "string",
  "teamOrgId": "00000000-0000-4000-8000-000000000000"
}

Responses

201Created ids with soft warnings
Content-Type: application/json
{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "membershipId": "00000000-0000-4000-8000-000000000000",
  "warnings": [
    "string"
  ],
  "duplicatePlayerIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}
422Missing or invalid identity fields
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/rosters/add-player" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"firstName\": \"string\",
  \"lastName\": \"string\",
  \"dateOfBirth\": \"string\",
  \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/rosters/guest-candidates
Auth requiredrosters.roster.guest

List in-club players outside the caller teams as guest candidates

The event editor's guest picker (in-club only): minimal fields, never the profile. consentStatus tells the caller whether a home-team-coach consent is required — pending for a coach, not_required for a coordinator or admin. Requires features: rosters.roster.guest

Responses

200Guest candidates page
Content-Type: application/json
{
  "items": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "firstName": null,
      "lastName": null,
      "homeTeamOrgId": "00000000-0000-4000-8000-000000000000",
      "shirtNumber": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "consentStatus": "not_required"
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/rosters/guest-candidates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/rosters/memberships
Auth requiredrosters.roster.view

List a player's memberships as an ordered team history

Every membership — live and ended — ordered by join date, scoped to the teams the caller can see. The history behind the profile page panel. Requires features: rosters.roster.view

Responses

200Membership history
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "playerId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "status": "active",
      "joinedAt": "string",
      "leftAt": null,
      "shirtNumber": null,
      "position": null,
      "transferId": null
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Player not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/rosters/memberships" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/rosters/transfers
Auth requiredrosters.transfer.manage

Open a transfer between two club teams

Immediate mode ends the source membership and activates the destination in one action; transition mode keeps the player on both rosters until completed or cancelled. Club admin, or a coordinator overseeing both teams; a coach never. Requires features: rosters.transfer.manage

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "fromTeamOrgId": "00000000-0000-4000-8000-000000000000",
  "toTeamOrgId": "00000000-0000-4000-8000-000000000000",
  "mode": "immediate"
}

Responses

201Opened transfer id
Content-Type: application/json
{
  "transferId": "00000000-0000-4000-8000-000000000000"
}
404Player not found
Content-Type: application/json
{
  "error": "string"
}
409Window already open, not on source, or already on destination
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or unknown team
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/rosters/transfers" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"fromTeamOrgId\": \"00000000-0000-4000-8000-000000000000\",
  \"toTeamOrgId\": \"00000000-0000-4000-8000-000000000000\",
  \"mode\": \"immediate\"
}"
POST/rosters/transfers/cancel
Auth requiredrosters.transfer.manage

Cancel the open transition window for a player

Ends the destination membership — never the source — leaving the player where they started. Requires features: rosters.transfer.manage

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000"
}

Responses

200Cancelled
Content-Type: application/json
{
  "ok": true
}
409No open transfer
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/rosters/transfers/cancel" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/rosters/transfers/complete
Auth requiredrosters.transfer.manage

Complete the open transition window for a player

Ends the source membership and repoints the home org; the destination membership stays active. Requires features: rosters.transfer.manage

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000"
}

Responses

200Completed
Content-Type: application/json
{
  "ok": true
}
409No open transfer
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/rosters/transfers/complete" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\"
}"

Health monitoring

Showing 8 of 8 endpoints
GET/health_monitoring/compliance
Auth requiredhealth_monitoring.health.view

List every in-scope player with an expired, expiring, or missing health check

Derived per request from each player's most-recent check per type against today and the club threshold — nothing stored or swept. Scope: coordinator = overseen teams, admin = all teams. Requires features: health_monitoring.health.view

Responses

200Compliance items
Content-Type: application/json
{
  "items": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "firstName": null,
      "lastName": null,
      "teamOrgIds": [
        "00000000-0000-4000-8000-000000000000"
      ],
      "checkType": null,
      "expiresAt": null,
      "state": "expiring"
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/health_monitoring/compliance" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/health_monitoring/health-checks
Auth requiredhealth_monitoring.health.view

List a player's health checks with derived expiry states

The player's full check history, newest first, decrypted per request. Visibility follows live team memberships; the club admin reads club-wide. Requires features: health_monitoring.health.view

Responses

200Check history
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "playerId": "00000000-0000-4000-8000-000000000000",
      "checkType": "sports_physical",
      "result": null,
      "issuedAt": "string",
      "expiresAt": "string",
      "state": "ok",
      "fileUrl": "string",
      "enteredByUserId": "00000000-0000-4000-8000-000000000000",
      "createdAt": "string",
      "updatedAt": "string"
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Player not found or out of scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/health_monitoring/health-checks" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/health_monitoring/health-checks
Auth requiredhealth_monitoring.health.manage

Enter a health check (multipart, mandatory file)

multipart/form-data: playerId, checkType, issuedAt, expiresAt, optional result, and the mandatory `file` (photo/screenshot of the test result). An entry without a file is rejected. Requires features: health_monitoring.health.manage

Responses

201Created check id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Player not found or out of scope
Content-Type: application/json
{
  "error": "string"
}
422Missing file / invalid fields
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/health_monitoring/health-checks" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/health_monitoring/health-checks
Auth requiredhealth_monitoring.health.manage

Correct a health check (multipart, optional file replacement)

multipart/form-data: id plus corrected dates/result; a `file` part replaces the stored test-result file. Requires features: health_monitoring.health.manage

Responses

200Updated check id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Check or player out of scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid fields or replacement file
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/health_monitoring/health-checks" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/health_monitoring/health-checks/file
Auth requiredhealth_monitoring.health.view

Stream the attached test-result file (authorized broker)

Serves the decrypted photo/screenshot for one check to an in-scope caller. There is no public or anonymous link to a medical file. Requires features: health_monitoring.health.view

Responses

200The file bytes (inline)
Content-Type: application/json
"string"
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Check, player, or file out of scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/health_monitoring/health-checks/file" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/health_monitoring/physical-measurements
Auth requiredhealth_monitoring.health.view

List a player's measurement series in time order

The per-player series, decrypted per request and ordered by measurement date; filter by kind for one series. Visibility follows live team memberships. Requires features: health_monitoring.health.view

Responses

200Measurement series
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "playerId": "00000000-0000-4000-8000-000000000000",
      "kind": "fitness",
      "metric": "string",
      "value": 1,
      "unit": null,
      "measuredAt": "string",
      "enteredByUserId": "00000000-0000-4000-8000-000000000000",
      "createdAt": "string"
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Player not found or out of scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/health_monitoring/physical-measurements" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/health_monitoring/physical-measurements
Auth requiredhealth_monitoring.health.manage

Record a fitness / PHV / somatic measurement

Requires features: health_monitoring.health.manage

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "kind": "fitness",
  "metric": "string",
  "value": 1,
  "measuredAt": "string"
}

Responses

201Created measurement id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Player not found or out of scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/health_monitoring/physical-measurements" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"kind\": \"fitness\",
  \"metric\": \"string\",
  \"value\": 1,
  \"measuredAt\": \"string\"
}"
GET/health_monitoring/player-status
Auth requiredhealth_monitoring.health.view

Read a player's derived health-check status

Per-type derived states plus one advisory nonCompliant flag (an expired type, or nothing on file). Advisory only — never blocks a lineup. Requires features: health_monitoring.health.view

Responses

200Derived status
Content-Type: application/json
{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "statuses": [
    {
      "checkType": "sports_physical",
      "expiresAt": "string",
      "state": "ok"
    }
  ],
  "nonCompliant": true
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Player not found or out of scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/health_monitoring/player-status" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Cross-club transfers

Showing 8 of 8 endpoints
GET/cross_club_transfers
Auth requiredcross_club_transfers.transfer.manage

List the caller club's outgoing and incoming agreements

Natively tenant-scoped: each club sees only its own side of the agreements it is party to. Listing lazily resolves lapsed proposals to expired and lapsed open windows to cancelled. Requires features: cross_club_transfers.transfer.manage

Responses

200Agreements of the caller side
Content-Type: application/json
{
  "items": [
    {
      "agreementRef": "00000000-0000-4000-8000-000000000000",
      "direction": "string",
      "status": "string",
      "counterpartyTenantId": "00000000-0000-4000-8000-000000000000",
      "sourcePlayerId": "00000000-0000-4000-8000-000000000000",
      "destinationPlayerId": null,
      "destinationTeamOrgId": "00000000-0000-4000-8000-000000000000",
      "summary": {
        "displayName": "string"
      },
      "proposedBy": "00000000-0000-4000-8000-000000000000",
      "expiresAt": "string",
      "openedAt": null,
      "completedAt": null,
      "cancelledAt": null
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/cross_club_transfers" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/cross_club_transfers
Auth requiredcross_club_transfers.transfer.manage

Propose a cross-club transfer of one player

Creates the mirrored agreement pair (outgoing in the source club, incoming in the destination). Carries only the source-authored summary - no encrypted personal data leaves the source club. Source club admin only. Requires features: cross_club_transfers.transfer.manage

Request body (application/json)

{
  "sourcePlayerId": "00000000-0000-4000-8000-000000000000",
  "destinationTenantId": "00000000-0000-4000-8000-000000000000",
  "destinationTeamOrgId": "00000000-0000-4000-8000-000000000000",
  "summary": {
    "displayName": "string"
  }
}

Responses

201Created proposal
Content-Type: application/json
{
  "agreementRef": "00000000-0000-4000-8000-000000000000",
  "expiresAt": "string"
}
404Player not found in the source club
Content-Type: application/json
{
  "error": "string"
}
409An active agreement already exists for the player
Content-Type: application/json
{
  "error": "string"
}
422Invalid input, own club, or unknown destination
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/cross_club_transfers" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"sourcePlayerId\": \"00000000-0000-4000-8000-000000000000\",
  \"destinationTenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"destinationTeamOrgId\": \"00000000-0000-4000-8000-000000000000\",
  \"summary\": {
    \"displayName\": \"string\"
  }
}"
POST/cross_club_transfers/accept
Auth requiredcross_club_transfers.transfer.manage

Accept a proposed transfer (destination club admin)

Both clubs have now consented: the transition window opens, a single-record read grant is issued, and a transitioning destination membership appears. Requires features: cross_club_transfers.transfer.manage

Request body (application/json)

{
  "agreementRef": "00000000-0000-4000-8000-000000000000"
}

Responses

200Window opened
Content-Type: application/json
{
  "ok": true
}
404Not a party to this agreement
Content-Type: application/json
{
  "error": "string"
}
409Not proposed anymore (terminal, open, or expired)
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/cross_club_transfers/accept" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"agreementRef\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/cross_club_transfers/cancel
Auth requiredcross_club_transfers.transfer.manage

Cancel an open transition window (either club admin)

Revokes the destination's scoped access and ends its transitioning membership, leaving the player wholly in the source club exactly as before the proposal. Requires features: cross_club_transfers.transfer.manage

Request body (application/json)

{
  "agreementRef": "00000000-0000-4000-8000-000000000000"
}

Responses

200Cancelled
Content-Type: application/json
{
  "ok": true
}
404Not a party to this agreement
Content-Type: application/json
{
  "error": "string"
}
409Only an open window can be cancelled
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/cross_club_transfers/cancel" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"agreementRef\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/cross_club_transfers/complete
Auth requiredcross_club_transfers.transfer.manage

Confirm completion on the caller's side

Completion is two-sided: each club admin confirms their own side. Once both have confirmed, the handover runs - the record is re-protected under the destination club, the source membership ends, the source profile is archived, and the scoped grant is revoked. Requires features: cross_club_transfers.transfer.manage

Request body (application/json)

{
  "agreementRef": "00000000-0000-4000-8000-000000000000"
}

Responses

200Confirmation recorded; completed=true once both sides confirmed
Content-Type: application/json
{
  "completed": true,
  "destinationPlayerId": null
}
404Not a party, or the source record vanished
Content-Type: application/json
{
  "error": "string"
}
409Window not open or lapsed
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/cross_club_transfers/complete" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"agreementRef\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/cross_club_transfers/decline
Auth requiredcross_club_transfers.transfer.manage

Decline a proposed transfer (destination club admin)

Ends the agreement on both sides; nothing about the player ever crossed the boundary. Requires features: cross_club_transfers.transfer.manage

Request body (application/json)

{
  "agreementRef": "00000000-0000-4000-8000-000000000000"
}

Responses

200Declined
Content-Type: application/json
{
  "ok": true
}
404Not a party to this agreement
Content-Type: application/json
{
  "error": "string"
}
409Not proposed anymore
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/cross_club_transfers/decline" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"agreementRef\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/cross_club_transfers/record
Auth requiredcross_club_transfers.transfer.manage

Read the transferring player's record during the open window

Destination club admin only, via the single-record time-boxed grant. Each read is individually audited with both clubs. Read-only; the record becomes writable in the destination only after completion re-protects it there. Requires features: cross_club_transfers.transfer.manage

Responses

200The one granted record plus provenance
Content-Type: application/json
{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "firstName": null,
  "lastName": null,
  "dateOfBirth": null,
  "preferredPositions": null,
  "dominantFoot": null,
  "notes": null,
  "status": "string",
  "customerUserId": null,
  "provenance": {
    "sourceTenantId": "00000000-0000-4000-8000-000000000000",
    "agreementRef": "00000000-0000-4000-8000-000000000000"
  }
}
404No visible record (not open, revoked, or not a party)
Content-Type: application/json
{
  "error": "string"
}
409The open window lapsed and was cancelled
Content-Type: application/json
{
  "error": "string"
}
422Missing or malformed agreement ref
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/cross_club_transfers/record" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/cross_club_transfers/withdraw
Auth requiredcross_club_transfers.transfer.manage

Withdraw an un-accepted proposal (source club admin)

Only valid before acceptance - an open window resolves by completion or cancellation instead. Requires features: cross_club_transfers.transfer.manage

Request body (application/json)

{
  "agreementRef": "00000000-0000-4000-8000-000000000000"
}

Responses

200Withdrawn
Content-Type: application/json
{
  "ok": true
}
404Not a party to this agreement
Content-Type: application/json
{
  "error": "string"
}
409Already accepted or terminal
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/cross_club_transfers/withdraw" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"agreementRef\": \"00000000-0000-4000-8000-000000000000\"
}"

Facility reservations

Showing 5 of 5 endpoints
GET/facilities_reservations
Auth requiredfacilities_reservations.reservation.view

List reservations

Org-scoped to the teams the caller can see (coach and coordinator read their teams, the club admin all), each reservation carrying its locker rooms. Filters: id, team, pitch, date window. Requires features: facilities_reservations.reservation.view

Responses

200Reservations
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "pitchFacilityId": "00000000-0000-4000-8000-000000000000",
      "lockerFacilityIds": [
        "00000000-0000-4000-8000-000000000000"
      ],
      "startsAt": "string",
      "endsAt": "string",
      "title": null,
      "notes": null
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/facilities_reservations" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/facilities_reservations
Auth requiredfacilities_reservations.reservation.manage

Create a reservation

Assigns one pitch plus one-or-more locker rooms to one team for a window. An overlap on the same pitch or a shared locker room (tenant-wide) returns the informational already-reserved advisory (409, requiresConfirmation) instead of saving; resubmitting with confirm: true saves - the confirmation is transient and never persisted or attributed. Emits facilities_reservations.reservation.created. Requires features: facilities_reservations.reservation.manage

Request body (application/json)

{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "pitchFacilityId": "00000000-0000-4000-8000-000000000000",
  "lockerFacilityIds": [
    "00000000-0000-4000-8000-000000000000"
  ],
  "startsAt": "string",
  "endsAt": "string"
}

Responses

201Created reservation id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}
409Already reserved in those hours (advisory)
Content-Type: application/json
{
  "error": "string",
  "requiresConfirmation": true,
  "conflicts": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "pitchFacilityId": "00000000-0000-4000-8000-000000000000",
      "lockerFacilityIds": [
        "00000000-0000-4000-8000-000000000000"
      ],
      "startsAt": "string",
      "endsAt": "string",
      "title": null
    }
  ]
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/facilities_reservations" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\",
  \"pitchFacilityId\": \"00000000-0000-4000-8000-000000000000\",
  \"lockerFacilityIds\": [
    \"00000000-0000-4000-8000-000000000000\"
  ],
  \"startsAt\": \"string\",
  \"endsAt\": \"string\"
}"
PUT/facilities_reservations
Auth requiredfacilities_reservations.reservation.manage

Edit a reservation

Repitches, reslots, or re-windows a reservation; the overlap check re-runs (excluding the row itself) with the same flag-and-confirm advisory. Emits facilities_reservations.reservation.updated. Requires features: facilities_reservations.reservation.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000",
  "title": null,
  "notes": null
}

Responses

200Updated reservation id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Reservation not found
Content-Type: application/json
{
  "error": "string"
}
409Already reserved in those hours (advisory)
Content-Type: application/json
{
  "error": "string",
  "requiresConfirmation": true,
  "conflicts": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "pitchFacilityId": "00000000-0000-4000-8000-000000000000",
      "lockerFacilityIds": [
        "00000000-0000-4000-8000-000000000000"
      ],
      "startsAt": "string",
      "endsAt": "string",
      "title": null
    }
  ]
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/facilities_reservations" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"title\": null,
  \"notes\": null
}"
DELETE/facilities_reservations
Auth requiredfacilities_reservations.reservation.manage

Remove a reservation

Soft-deletes. While a match references the reservation the removal is refused (409) unless detachMatches: true clears every referencing match first - never a silent orphan. Emits facilities_reservations.reservation.cancelled. Requires features: facilities_reservations.reservation.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed
Content-Type: application/json
{
  "ok": true
}
404Reservation not found
Content-Type: application/json
{
  "error": "string"
}
409A match still references this reservation
Content-Type: application/json
{
  "error": "string",
  "attachedMatchIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/facilities_reservations" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/facilities_reservations/locker-schedule
Auth requiredfacilities_reservations.reservation.view

Read the locker-room schedule

Which locker room serves each reservation-attached scheduled match - derived on every read from the reservations and the match calendar, storing nothing. Cancelled matches drop off; reservation and match changes show on the next read. Scoped to the teams the caller can see and always to the club. Requires features: facilities_reservations.reservation.view

Responses

200Locker-room x match rows
Content-Type: application/json
{
  "items": [
    {
      "lockerFacilityId": "00000000-0000-4000-8000-000000000000",
      "lockerFacilityName": null,
      "reservationId": "00000000-0000-4000-8000-000000000000",
      "reservationStartsAt": "string",
      "reservationEndsAt": "string",
      "pitchFacilityId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "matchId": "00000000-0000-4000-8000-000000000000",
      "matchStartsAt": "string",
      "opponentName": null,
      "homeAway": null
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/facilities_reservations/locker-schedule" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

User profile

Showing 3 of 3 endpoints
GET/user_profile/me
Auth required

Read the caller's own optional profile

Always resolves to the session subject; every field is optional and an all-null profile is the normal state.

Responses

200The profile
Content-Type: application/json
{
  "photoUrl": null,
  "coachingLicenseNumber": null,
  "coachingLicenseExpiresOn": null,
  "phone": null,
  "bio": null
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/user_profile/me" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/user_profile/me
Auth required

Update the caller's own optional profile

Partial write: absent fields stay untouched, null (or an empty string) clears. No field is required and nothing anywhere depends on any of them.

Request body (application/json)

{
  "photoUrl": null,
  "coachingLicenseNumber": null,
  "coachingLicenseExpiresOn": null,
  "phone": null,
  "bio": null
}

Responses

200The updated profile
Content-Type: application/json
{
  "photoUrl": null,
  "coachingLicenseNumber": null,
  "coachingLicenseExpiresOn": null,
  "phone": null,
  "bio": null
}
422Validation error
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/user_profile/me" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"photoUrl\": null,
  \"coachingLicenseNumber\": null,
  \"coachingLicenseExpiresOn\": null,
  \"phone\": null,
  \"bio\": null
}"
GET/user_profile/staff-compliance
Auth requireduser_profile.staff_compliance.view

List in-scope coaches with their license {number, expiry} and derived status

Derived per request against the inclusive 183-day (~6-month) window — nothing stored, no push. Scope: club admin = all coaches, coordinator = coaches on overseen teams. Requires features: user_profile.staff_compliance.view

Responses

200Compliance items
Content-Type: application/json
{
  "items": [
    {
      "userId": "00000000-0000-4000-8000-000000000000",
      "name": null,
      "email": null,
      "teamOrgIds": [
        "00000000-0000-4000-8000-000000000000"
      ],
      "coachingLicenseNumber": null,
      "coachingLicenseExpiresOn": null,
      "status": "ok"
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/user_profile/staff-compliance" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Surveys and RPE

Showing 11 of 11 endpoints
GET/surveys_rpe
Auth requiredsurveys_rpe.survey.view

List RPE and wellness responses

The per-player survey history attached to the persistent player profile: filter by session, player, team, or template type. Scoped staff read only their visible team-orgs; the history follows the player across roster changes and transfers. Requires features: surveys_rpe.survey.view

Responses

200Responses
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "sessionId": "00000000-0000-4000-8000-000000000000",
      "playerId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "templateId": null,
      "templateType": "rpe",
      "rpe": null,
      "answers": {
        "key": "string"
      },
      "source": "staff",
      "submittedByUserId": null,
      "submittedAt": "string",
      "exerciseLines": [
        {
          "label": "string",
          "rpe": 1,
          "sortOrder": 1
        }
      ]
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Out-of-scope team or session
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/surveys_rpe" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/surveys_rpe/consents
Auth requiredsurveys_rpe.survey.view

List a player's live recorded consents

Requires features: surveys_rpe.survey.view

Responses

200Consents
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "playerId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": null,
      "guardianName": "string",
      "grantedByUserId": "00000000-0000-4000-8000-000000000000",
      "grantedAt": "string"
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Player out of scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/surveys_rpe/consents" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/surveys_rpe/consents
Auth requiredsurveys_rpe.survey.manage

Record a guardian consent for a player

Files the guardian's consent so a minor's RPE/wellness capture passes the club's consent gate; a re-grant corrects the single live row. Requires features: surveys_rpe.survey.manage

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "guardianName": "string"
}

Responses

200The recorded consent
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "playerId": "00000000-0000-4000-8000-000000000000",
  "teamOrgId": null,
  "guardianName": "string",
  "grantedByUserId": "00000000-0000-4000-8000-000000000000",
  "grantedAt": "string"
}
404Player out of scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/surveys_rpe/consents" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"guardianName\": \"string\"
}"
GET/surveys_rpe/sessions/{id}/kiosk
Auth requiredsurveys_rpe.survey.manage

List the kiosk's selectable roster names

The session team's active-membership roster as selectable names with per-player submitted flags - only names, never another player's answers. A cancelled session offers no names. Requires features: surveys_rpe.survey.manage

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200The selectable roster
Content-Type: application/json
{
  "session": {
    "id": "00000000-0000-4000-8000-000000000000",
    "status": "string"
  },
  "rows": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "firstName": null,
      "lastName": null,
      "hasRpe": true,
      "hasWellness": true
    }
  ]
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/surveys_rpe/sessions/:id/kiosk" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/surveys_rpe/sessions/{id}/kiosk
Auth requiredsurveys_rpe.survey.manage

Submit one player's own RPE/wellness survey with no login

A player selects their name and answers; the response saves to their profile with source 'kiosk' and no personal login, within the opening staff's team scope. The kiosk never takes attendance, and in hybrid mode an already-recorded response stands (first-write-wins). Requires features: surveys_rpe.survey.manage

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000"
}

Responses

200Capture outcomes
Content-Type: application/json
{
  "results": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "templateType": "string",
      "outcome": "created"
    }
  ]
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled session, capture-mode mismatch, or missing minor consent
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or off-roster player
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/surveys_rpe/sessions/:id/kiosk" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/surveys_rpe/sessions/{id}/sheet
Auth requiredsurveys_rpe.survey.view

Read the roster survey sheet for one session

The derived sheet the coach (or kiosk) fills, in one request: the session team's memberships active at session time joined to the decrypted player identity, each player's current RPE (with exercise breakdown) and wellness responses, the attendance state for the combined roll-call, and the resolved RPE/wellness templates with their capture modes. A cancelled session returns the sheet closed (no row markable). Requires features: surveys_rpe.survey.view

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200The survey sheet
Content-Type: application/json
{
  "session": {
    "id": "00000000-0000-4000-8000-000000000000",
    "teamOrgId": "00000000-0000-4000-8000-000000000000",
    "eventType": "string",
    "status": "string",
    "startsAt": "string"
  },
  "templates": {
    "rpe": null,
    "wellness": null,
    "rpeCaptureMode": "string",
    "wellnessCaptureMode": "string"
  },
  "rows": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "firstName": null,
      "lastName": null,
      "markable": true,
      "rpe": null,
      "wellness": null,
      "attendanceStatus": null
    }
  ]
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/surveys_rpe/sessions/:id/sheet" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/surveys_rpe/sessions/{id}/sheet
Auth requiredsurveys_rpe.survey.manage

Capture the whole sheet in one pass (combined roll-call)

Upserts per player at most one RPE response (with its in-survey exercise breakdown) and one wellness response on (session, player, type), honoring the capture-mode source gate, the cancelled-session reject, and the minors consent gate; an optional attendanceStatus per entry upserts the independent attendance record in the same pass. In hybrid mode an existing response from the other path is kept, first-write-wins. Requires features: surveys_rpe.survey.manage

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "entries": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000"
    }
  ]
}

Responses

200Capture outcomes per written response
Content-Type: application/json
{
  "results": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "playerId": "00000000-0000-4000-8000-000000000000",
      "templateType": "string",
      "outcome": "created"
    }
  ]
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled session, capture-mode mismatch, or missing minor consent
Content-Type: application/json
{
  "error": "string"
}
422Invalid input, off-roster player, or invalid answers
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/surveys_rpe/sessions/:id/sheet" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entries\": [
    {
      \"playerId\": \"00000000-0000-4000-8000-000000000000\"
    }
  ]
}"
GET/surveys_rpe/settings
Auth requiredsurveys_rpe.survey.view

Read a team's default capture mode

Requires features: surveys_rpe.survey.view

Responses

200The stored default (null = inherit coach_panel)
Content-Type: application/json
{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "defaultCaptureMode": null
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team out of scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/surveys_rpe/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/surveys_rpe/settings
Auth requiredsurveys_rpe.template.manage

Set a team's default capture mode

Stores the team-level default a survey without an explicit capture mode inherits. Coach on that team or club_admin; a coordinator never configures surveys. Requires features: surveys_rpe.template.manage

Request body (application/json)

{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "defaultCaptureMode": "self_report"
}

Responses

200The stored default
Content-Type: application/json
{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "defaultCaptureMode": null
}
422Unrecognized capture mode
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/surveys_rpe/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\",
  \"defaultCaptureMode\": \"self_report\"
}"
GET/surveys_rpe/templates
Auth requiredsurveys_rpe.survey.view

List the club default and per-team survey templates

Requires features: surveys_rpe.survey.view

Responses

200Templates
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": null,
      "templateType": "string",
      "captureMode": null,
      "isDefault": true,
      "definition": {
        "questions": [
          {}
        ]
      },
      "createdByUserId": null
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/surveys_rpe/templates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/surveys_rpe/templates
Auth requiredsurveys_rpe.template.manage

Create or correct one survey template

Upserts the single live template per (type, scope): the reused club default RPE survey (teamOrgId null, club_admin only) or a per-team wellness survey composed from the question bank plus custom fields (coach on that team or club_admin). A coordinator never configures surveys. Requires features: surveys_rpe.template.manage

Request body (application/json)

{
  "templateType": "rpe",
  "teamOrgId": null,
  "captureMode": null,
  "definition": {
    "questions": [
      {
        "id": "string",
        "label": "string",
        "type": "scale_1_5",
        "required": true
      }
    ]
  }
}

Responses

200The saved template
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "teamOrgId": null,
  "templateType": "string",
  "captureMode": null,
  "isDefault": true,
  "definition": {
    "questions": [
      {}
    ]
  },
  "createdByUserId": null
}
422Invalid definition or capture mode
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/surveys_rpe/templates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"templateType\": \"rpe\",
  \"teamOrgId\": null,
  \"captureMode\": null,
  \"definition\": {
    \"questions\": [
      {
        \"id\": \"string\",
        \"label\": \"string\",
        \"type\": \"scale_1_5\",
        \"required\": true
      }
    ]
  }
}"

AI training plans

Showing 14 of 14 endpoints
GET/ai_training_plans/library/exercises
Auth requiredai_training_plans.library.view

List the visible library exercises - global product-default, own club tier, own private entries

Requires features: ai_training_plans.library.view

Responses

200Exercises
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "tier": "string",
      "content": {},
      "ownerUserId": null,
      "copiedFromId": null,
      "importId": null,
      "createdAt": "string"
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/library/exercises" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/ai_training_plans/library/exercises
Auth requiredai_training_plans.library.manage

Save an exercise - always an insert; a copy-edit sets copiedFromId and never overwrites the source

Requires features: ai_training_plans.library.manage

Request body (application/json)

{
  "content": {
    "label": "string"
  },
  "shareToClub": true
}

Responses

201The saved entry
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "tier": "string",
  "content": {},
  "ownerUserId": null,
  "copiedFromId": null,
  "importId": null,
  "createdAt": "string"
}
404Copy source out of scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid content
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/library/exercises" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"content\": {
    \"label\": \"string\"
  },
  \"shareToClub\": true
}"
GET/ai_training_plans/library/imports
Auth requiredai_training_plans.library.view

List the club import ingestion records with their status

Requires features: ai_training_plans.library.view

Responses

200Imports
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "sourceFormat": "string",
      "importStatus": "string",
      "error": null,
      "attachmentId": null,
      "createdAt": "string"
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/library/imports" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/ai_training_plans/library/imports
Auth requiredai_training_plans.library.manage

Upload one office file (multipart `file` + optional `shareToClub`); a parse failure leaves a failed record and no entry

Requires features: ai_training_plans.library.manage

Responses

201The ingestion record and the created entry
Content-Type: application/json
{
  "import": {
    "id": "00000000-0000-4000-8000-000000000000",
    "sourceFormat": "string",
    "importStatus": "string",
    "error": null,
    "attachmentId": null,
    "createdAt": "string"
  }
}
422Unsupported, empty, or unreadable file
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/library/imports" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/ai_training_plans/library/outline-templates
Auth requiredai_training_plans.library.view

List the visible outline templates across the three tiers

Requires features: ai_training_plans.library.view

Responses

200Templates
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "tier": "string",
      "content": {},
      "ownerUserId": null,
      "copiedFromId": null,
      "createdAt": "string"
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/library/outline-templates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/ai_training_plans/library/outline-templates
Auth requiredai_training_plans.library.manage

Save a template - always an insert; the curated base is never overwritten

Requires features: ai_training_plans.library.manage

Request body (application/json)

{
  "content": {
    "blocks": [
      {
        "label": "string"
      }
    ]
  },
  "shareToClub": true
}

Responses

201The saved template
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "tier": "string",
  "content": {},
  "ownerUserId": null,
  "copiedFromId": null,
  "createdAt": "string"
}
404Copy source out of scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid content
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/library/outline-templates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"content\": {
    \"blocks\": [
      {
        \"label\": \"string\"
      }
    ]
  },
  \"shareToClub\": true
}"
GET/ai_training_plans/plans
Auth requiredai_training_plans.plan.view

List the plans of the teams in the caller scope, filterable by team, status, and session

Requires features: ai_training_plans.plan.view

Responses

200Plans
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "prompt": "string",
      "title": null,
      "status": "string",
      "outline": null,
      "generatedOutline": null,
      "sessionId": null,
      "outlineTemplateId": null,
      "generatedAt": null,
      "generationError": null,
      "createdAt": "string"
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/plans" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/ai_training_plans/plans
Auth requiredai_training_plans.plan.manage

Save outline/title edits - the generated outline, prompt, and provenance stay immutable

Requires features: ai_training_plans.plan.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200The edited plan
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "prompt": "string",
  "title": null,
  "status": "string",
  "outline": null,
  "generatedOutline": null,
  "sessionId": null,
  "outlineTemplateId": null,
  "generatedAt": null,
  "generationError": null,
  "createdAt": "string"
}
404Plan out of scope
Content-Type: application/json
{
  "error": "string"
}
409The plan has no editable outline yet
Content-Type: application/json
{
  "error": "string"
}
422Invalid patch
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/plans" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
DELETE/ai_training_plans/plans
Auth requiredai_training_plans.plan.manage

Discard a generation (soft delete) - the input layer is untouched

Requires features: ai_training_plans.plan.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Discarded
Content-Type: application/json
{
  "ok": true
}
404Plan out of scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/plans" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/ai_training_plans/plans/generate
Auth requiredai_training_plans.plan.manage

Explicit on-demand generation grounded in the club game model, the academy corpus, picked library entries, and the derived coach history - never auto-applied

Requires features: ai_training_plans.plan.manage

Request body (application/json)

{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "prompt": "string",
  "libraryExerciseIds": []
}

Responses

201The generated (or failed) plan
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "status": "string"
}
404Team or session out of scope
Content-Type: application/json
{
  "error": "string"
}
409The referenced session is cancelled or not a training
Content-Type: application/json
{
  "error": "string"
}
422Invalid prompt
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/plans/generate" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\",
  \"prompt\": \"string\",
  \"libraryExerciseIds\": []
}"
GET/ai_training_plans/settings/academy-model
Auth requiredclubs.settings.view

Read the active corpus status for the club

Requires features: clubs.settings.view

Responses

200The active corpus or null
Content-Type: application/json
{
  "corpus": null
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/settings/academy-model" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/ai_training_plans/settings/academy-model
Auth requiredclubs.settings.manage

Upload/replace the academy document (multipart `file`); it primes every subsequent generation

Requires features: clubs.settings.manage

Responses

200The new active corpus
Content-Type: application/json
{
  "corpus": {
    "id": "00000000-0000-4000-8000-000000000000",
    "fileName": "string",
    "active": true,
    "textLength": 1,
    "uploadedAt": "string"
  }
}
422Unsupported or unreadable file
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/settings/academy-model" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/ai_training_plans/settings/game-model
Auth requiredclubs.settings.view

Read the tenant game-model blob merged over defaults

Requires features: clubs.settings.view

Responses

200The game model
Content-Type: application/json
{
  "gameModel": {
    "playingModel": "",
    "sampleMethods": []
  }
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/settings/game-model" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/ai_training_plans/settings/game-model
Auth requiredclubs.settings.manage

Replace the playing model + sample methods (club admin)

Requires features: clubs.settings.manage

Request body (application/json)

{
  "playingModel": "string",
  "sampleMethods": []
}

Responses

200The saved game model
Content-Type: application/json
{
  "gameModel": {
    "playingModel": "",
    "sampleMethods": []
  }
}
422Empty or invalid playing model
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/ai_training_plans/settings/game-model" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playingModel\": \"string\",
  \"sampleMethods\": []
}"

Competition and results

Showing 3 of 3 endpoints
GET/competition_and_results
Auth requiredcompetition_and_results.result.view

List match results

The results read, scoped to the caller's visible teams (a delegated match-coach additionally reads the one delegated match). Each item joins the opponent name and kickoff from the linked match, so a results list is one request. Filters: session, team, outcome. Requires features: competition_and_results.result.view

Responses

200Match results
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "sessionId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "outcome": "win",
      "ownScore": null,
      "opponentScore": null,
      "note": null,
      "opponentName": null,
      "startsAt": null,
      "recordedByUserId": null,
      "recordedAt": "string"
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team or match not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/competition_and_results" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/competition_and_results
Auth requiredcompetition_and_results.result.manage

Record or correct the match result

Upserts the single live result for the match: an outcome (win/loss/draw from the own team's perspective) with an optional own/opponent score, consistency-checked when both scores are present. Rejected when the session is not a match (422), is cancelled (409), or is out of the caller's per-team scope (404). Requires features: competition_and_results.result.manage

Request body (application/json)

{
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "outcome": "win"
}

Responses

200Recorded result id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Match not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled match
Content-Type: application/json
{
  "error": "string"
}
422Invalid input, inconsistent outcome, or not a match
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/competition_and_results" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"outcome\": \"win\"
}"
DELETE/competition_and_results
Auth requiredcompetition_and_results.result.manage

Clear the match result

Soft-deletes the single live result so the match returns to "no result" and is excluded from any derived record until re-recorded. Requires features: competition_and_results.result.manage

Request body (application/json)

{
  "sessionId": "00000000-0000-4000-8000-000000000000"
}

Responses

200Cleared result id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Match or result not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled match
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or not a match
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/competition_and_results" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\"
}"

Statistics and analytics

Showing 10 of 10 endpoints
GET/statistics_and_analytics/player-groups
Auth requiredstatistics_and_analytics.statistics.view

List the visible teams' player groups with their memberships

Requires features: statistics_and_analytics.statistics.view

Responses

200Player groups
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "description": null,
      "memberPlayerIds": [
        "00000000-0000-4000-8000-000000000000"
      ]
    }
  ]
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/statistics_and_analytics/player-groups" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/statistics_and_analytics/player-groups
Auth requiredstatistics_and_analytics.player_group.manage

Create a named group on one team, optionally seeded with members

The group belongs to exactly one team and its members FK the persistent club player, so a membership survives roster changes and transfers. Rejected out of the caller's per-team scope (404) or when a member is not a club player (422). Requires features: statistics_and_analytics.player_group.manage

Request body (application/json)

{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "memberPlayerIds": []
}

Responses

200Created group id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/statistics_and_analytics/player-groups" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\",
  \"memberPlayerIds\": []
}"
PATCH/statistics_and_analytics/player-groups
Auth requiredstatistics_and_analytics.player_group.manage

Rename a group (a team move is not expressible)

Requires features: statistics_and_analytics.player_group.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000",
  "name": "string"
}

Responses

200Renamed group id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Group not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PATCH "https://trainmaxr.pboj.smallhost.pl/api/statistics_and_analytics/player-groups" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\"
}"
DELETE/statistics_and_analytics/player-groups
Auth requiredstatistics_and_analytics.player_group.manage

Delete a group and its membership; the underlying statistics are untouched

Requires features: statistics_and_analytics.player_group.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Deleted group id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Group not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/statistics_and_analytics/player-groups" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/statistics_and_analytics/player-groups/members
Auth requiredstatistics_and_analytics.player_group.manage

Add a club player to a group (idempotent for an existing member)

Requires features: statistics_and_analytics.player_group.manage

Request body (application/json)

{
  "groupId": "00000000-0000-4000-8000-000000000000",
  "playerId": "00000000-0000-4000-8000-000000000000"
}

Responses

200Member id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Group not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or not a club player
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/statistics_and_analytics/player-groups/members" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"groupId\": \"00000000-0000-4000-8000-000000000000\",
  \"playerId\": \"00000000-0000-4000-8000-000000000000\"
}"
DELETE/statistics_and_analytics/player-groups/members
Auth requiredstatistics_and_analytics.player_group.manage

Remove a player from a group; the group aggregates recompute over the rest

Requires features: statistics_and_analytics.player_group.manage

Request body (application/json)

{
  "groupId": "00000000-0000-4000-8000-000000000000",
  "playerId": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed member id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Group or member not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/statistics_and_analytics/player-groups/members" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"groupId\": \"00000000-0000-4000-8000-000000000000\",
  \"playerId\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/statistics_and_analytics/statistics/group
Auth requiredstatistics_and_analytics.statistics.view

The same aggregates as the team read, restricted to the group's member set

An empty group reads zero aggregates rather than erroring. Accepts an optional from/to period. Requires features: statistics_and_analytics.statistics.view

Responses

200Group statistics
Content-Type: application/json
{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "playerCount": 1,
  "totals": {
    "goals": 1,
    "assists": 1,
    "yellowCards": 1,
    "redCards": 1,
    "minutesPlayed": 1,
    "starterAppearances": 1,
    "substituteAppearances": 1,
    "cleanSheets": 1,
    "goalsConcededOnPitch": 1,
    "trainingSessions": 1
  },
  "averages": {
    "goals": 1,
    "assists": 1,
    "yellowCards": 1,
    "redCards": 1,
    "minutesPlayed": 1,
    "starterAppearances": 1,
    "substituteAppearances": 1,
    "cleanSheets": 1,
    "goalsConcededOnPitch": 1,
    "trainingSessions": 1
  },
  "timePerTrainingElement": [
    {
      "categoryId": "string",
      "categoryName": null,
      "minutes": 1
    }
  ],
  "players": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "goals": 1,
      "assists": 1,
      "yellowCards": 1,
      "redCards": 1,
      "minutesPlayed": 1,
      "starterAppearances": 1,
      "substituteAppearances": 1,
      "cleanSheets": 1,
      "goalsConcededOnPitch": 1,
      "trainingSessions": 1
    }
  ],
  "groupId": "00000000-0000-4000-8000-000000000000"
}
404Group not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid query or period
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/statistics_and_analytics/statistics/group" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/statistics_and_analytics/statistics/player
Auth requiredstatistics_and_analytics.statistics.view

A player's goals, assists, cards, minutes (total and per month), appearances split, training sessions, time per training element, clean sheets, and goals conceded while on the pitch

Computed live over the input layer (PRD 02 derived data) and scoped to the caller's visible teams: a coach reads only the assigned-team portion of the career, the club admin the whole. Clean sheets and goals-conceded are interval-accurate over the player's on-pitch intervals from the recorded goal minutes. Team-level tagging metrics are never part of this read. Accepts an optional from/to period. Requires features: statistics_and_analytics.statistics.view

Responses

200Player statistics
Content-Type: application/json
{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "goals": 1,
  "assists": 1,
  "yellowCards": 1,
  "redCards": 1,
  "minutesPlayed": 1,
  "starterAppearances": 1,
  "substituteAppearances": 1,
  "cleanSheets": 1,
  "goalsConcededOnPitch": 1,
  "trainingSessions": 1,
  "minutesPerMonth": [
    {
      "month": "string",
      "minutes": 1
    }
  ],
  "timePerTrainingElement": [
    {
      "categoryId": "string",
      "categoryName": null,
      "minutes": 1
    }
  ]
}
404Player not found in any visible team
Content-Type: application/json
{
  "error": "string"
}
422Invalid query or period
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/statistics_and_analytics/statistics/player" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/statistics_and_analytics/statistics/rankings
Auth requiredstatistics_and_analytics.statistics.view

Teams and top players ranked across the teams the caller may see

The coordinator sees everything on overseen teams, including cross-team rankings; the org-less club admin ranks every club team. The broad academy KPI catalogue is assembled by the reporting slice on top of these figures. Accepts an optional from/to period. Requires features: statistics_and_analytics.statistics.view

Responses

200Rankings
Content-Type: application/json
{
  "teams": [
    {
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "goals": 1,
      "assists": 1,
      "minutesPlayed": 1
    }
  ],
  "players": [
    {
      "goals": 1,
      "assists": 1,
      "minutesPlayed": 1,
      "playerId": "00000000-0000-4000-8000-000000000000",
      "teamOrgIds": [
        "00000000-0000-4000-8000-000000000000"
      ]
    }
  ]
}
422Invalid period
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/statistics_and_analytics/statistics/rankings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/statistics_and_analytics/statistics/team
Auth requiredstatistics_and_analytics.statistics.view

The individual metrics aggregated over the team's players as totals and per-player averages, with a per-player table

Aggregation is over the input records of the team's scope, so a mid-transfer player contributes only that team's rows - never double-counted. Team-level tagging metrics (shots/tackles/fouls) are not part of this aggregate. Accepts an optional from/to period. Requires features: statistics_and_analytics.statistics.view

Responses

200Team statistics
Content-Type: application/json
{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "playerCount": 1,
  "totals": {
    "goals": 1,
    "assists": 1,
    "yellowCards": 1,
    "redCards": 1,
    "minutesPlayed": 1,
    "starterAppearances": 1,
    "substituteAppearances": 1,
    "cleanSheets": 1,
    "goalsConcededOnPitch": 1,
    "trainingSessions": 1
  },
  "averages": {
    "goals": 1,
    "assists": 1,
    "yellowCards": 1,
    "redCards": 1,
    "minutesPlayed": 1,
    "starterAppearances": 1,
    "substituteAppearances": 1,
    "cleanSheets": 1,
    "goalsConcededOnPitch": 1,
    "trainingSessions": 1
  },
  "timePerTrainingElement": [
    {
      "categoryId": "string",
      "categoryName": null,
      "minutes": 1
    }
  ],
  "players": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "goals": 1,
      "assists": 1,
      "yellowCards": 1,
      "redCards": 1,
      "minutesPlayed": 1,
      "starterAppearances": 1,
      "substituteAppearances": 1,
      "cleanSheets": 1,
      "goalsConcededOnPitch": 1,
      "trainingSessions": 1
    }
  ]
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid query or period
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/statistics_and_analytics/statistics/team" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Workload and fatigue monitoring

Showing 7 of 7 endpoints
GET/workload_and_fatigue_monitoring/fatigue/cross-team
Auth requiredworkload_and_fatigue_monitoring.fatigue.cross_team_view

Every red/orange-flagged player across the caller visible teams (a coordinator overseen set; the whole club for an admin), worst first

Requires features: workload_and_fatigue_monitoring.fatigue.cross_team_view

Responses

200Red/orange players
Content-Type: application/json
{
  "players": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "value": 1,
      "band": "orange"
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/workload_and_fatigue_monitoring/fatigue/cross-team" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/workload_and_fatigue_monitoring/fatigue/current
Auth requiredworkload_and_fatigue_monitoring.fatigue.view

Each roster player's time-decayed fatigue value and band, with the forecast onto the next scheduled events up to the next match

Requires features: workload_and_fatigue_monitoring.fatigue.view

Responses

200Current fatigue + forecast
Content-Type: application/json
{
  "players": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "value": 1,
      "band": "green",
      "forecast": [
        {
          "sessionId": "00000000-0000-4000-8000-000000000000",
          "startsAt": "string",
          "eventType": "training",
          "value": 1,
          "band": "green"
        }
      ]
    }
  ]
}
404Team not visible in the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/workload_and_fatigue_monitoring/fatigue/current" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/workload_and_fatigue_monitoring/fatigue/live
Auth requiredworkload_and_fatigue_monitoring.fatigue.view

Each roster player's trailing-7-day fatigue percent and band, plus the team age-group average

Requires features: workload_and_fatigue_monitoring.fatigue.view

Responses

200Live fatigue readings
Content-Type: application/json
{
  "players": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "value": 1,
      "band": "green"
    }
  ],
  "teamAverage": 1
}
404Team not visible in the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/workload_and_fatigue_monitoring/fatigue/live" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/workload_and_fatigue_monitoring/fatigue/min-minutes
Auth requiredworkload_and_fatigue_monitoring.fatigue.view

Players below the team's minimum share of available match minutes in the period

Requires features: workload_and_fatigue_monitoring.fatigue.view

Responses

200Min-minutes report
Content-Type: application/json
{
  "periodFrom": "string",
  "periodTo": "string",
  "minMinutesPct": 1,
  "availableMinutes": 1,
  "flagged": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "playedMinutes": 1,
      "availableMinutes": 1,
      "sharePct": 1
    }
  ]
}
404Team not visible in the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid query or period
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/workload_and_fatigue_monitoring/fatigue/min-minutes" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/workload_and_fatigue_monitoring/fatigue/workload
Auth requiredworkload_and_fatigue_monitoring.fatigue.view

A team's microcycle load, per-player individual loads, and the light/moderate/heavy classification

Requires features: workload_and_fatigue_monitoring.fatigue.view

Responses

200Microcycle workload
Content-Type: application/json
{
  "window": {
    "weekStart": "string",
    "weekEnd": "string",
    "isoWeek": "string"
  },
  "teamLoadAu": 1,
  "classification": "light",
  "sessions": [
    {
      "sessionId": "00000000-0000-4000-8000-000000000000",
      "eventType": "training",
      "startsAt": "string",
      "durationMinutes": null,
      "missingDuration": true,
      "submitterCount": 1,
      "meanRpe": null,
      "teamLoadAu": 1
    }
  ],
  "players": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "loadAu": 1,
      "sessions": [
        {
          "sessionId": "00000000-0000-4000-8000-000000000000",
          "srpeAu": 1
        }
      ]
    }
  ]
}
404Team not visible in the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/workload_and_fatigue_monitoring/fatigue/workload" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/workload_and_fatigue_monitoring/team-workload-config
Auth requiredworkload_and_fatigue_monitoring.fatigue.view

Read a team's effective workload configuration (code defaults when no row exists)

Requires features: workload_and_fatigue_monitoring.fatigue.view

Responses

200Effective configuration
Content-Type: application/json
{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "heavyThresholdAu": 1,
  "moderatePct": 1,
  "minMinutesPct": 1,
  "rpeCompletionPct": 1,
  "ewmaMatchWeight": 1,
  "ewmaTrainingWeight": 1,
  "ewmaRecoveryHalfLifeHours": 1
}
404Team not visible in the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/workload_and_fatigue_monitoring/team-workload-config" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/workload_and_fatigue_monitoring/team-workload-config
Auth requiredworkload_and_fatigue_monitoring.fatigue.configure

Create or edit a team's thresholds and EWMA parameters (coordinator/admin)

Requires features: workload_and_fatigue_monitoring.fatigue.configure

Responses

200Saved configuration row
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "teamOrgId": "00000000-0000-4000-8000-000000000000"
}
404Team not visible in the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid threshold or EWMA values
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/workload_and_fatigue_monitoring/team-workload-config" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Reporting and filtering

Showing 8 of 8 endpoints
GET/reporting_and_filtering/reports/compare-teams
Auth requiredreporting_and_filtering.reporting.view

Pick teams (within the caller scope) and read the KPI comparison: top-8 academy header + per-metric COUNT, AVERAGE, TREND, RANKING, COMPARISON

The selected team-org ids are intersected with the caller scope server-side (coordinator = overseen, admin = all); the header rolls up the whole scope. The portal visibility setting never gates this staff surface. Requires features: reporting_and_filtering.reporting.view

Responses

200Comparison table
Content-Type: application/json
{
  "period": {
    "source": "app"
  },
  "header": [
    {
      "key": "string",
      "value": 1
    }
  ],
  "metrics": [
    {
      "key": "string",
      "teams": [
        {
          "teamOrgId": "string",
          "count": 1,
          "average": 1,
          "trend": {
            "firstHalf": 1,
            "secondHalf": 1,
            "direction": "string"
          },
          "rank": 1,
          "comparison": {
            "selectionAverage": 1,
            "delta": 1
          }
        }
      ]
    }
  ]
}
404A selected team is outside the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Empty or invalid team selection or period
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/reporting_and_filtering/reports/compare-teams" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/reporting_and_filtering/reports/group
Auth requiredreporting_and_filtering.reporting.view

The group report filtered by period (default: the resolved current season for the group team) and by specific statistic

Requires features: reporting_and_filtering.reporting.view

Responses

200Group report
Content-Type: application/json
{
  "groupId": "00000000-0000-4000-8000-000000000000",
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "playerCount": 1,
  "period": {
    "source": "app"
  },
  "statistics": [
    "string"
  ],
  "metrics": {}
}
404Group not visible in the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid period or statistic selection
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/reporting_and_filtering/reports/group" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/reporting_and_filtering/reports/player
Auth requiredreporting_and_filtering.reporting.view

The individual report filtered by period (default: the resolved current season) and by specific statistic

Wraps the statistics-and-analytics player computation: with no explicit from/to the period defaults to the season resolved for the optional team context (team override, club override, app default - nearest wins). The statistics param narrows the catalogue; an unknown or team-only key is rejected. Keys on the persistent player, scoped to the caller-visible teams. Requires features: reporting_and_filtering.reporting.view

Responses

200Player report
Content-Type: application/json
{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "period": {
    "source": "app"
  },
  "statistics": [
    "string"
  ],
  "metrics": {}
}
404Player or team not visible in the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid period or statistic selection
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/reporting_and_filtering/reports/player" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/reporting_and_filtering/reports/team
Auth requiredreporting_and_filtering.reporting.view

The team report filtered by period (default: the resolved current season) and by specific statistic

The full catalogue: appearances/minutes, goals/assists/shooting, cards, clean sheets, training realisation with the per-type time breakdown, the live workload/fatigue snapshot, team results/points/form, and coach-report compliance (counts, 48h timeliness, gaps). Requires features: reporting_and_filtering.reporting.view

Responses

200Team report
Content-Type: application/json
{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "playerCount": 1,
  "period": {
    "source": "app"
  },
  "statistics": [
    "string"
  ],
  "metrics": {}
}
404Team not visible in the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid period or statistic selection
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/reporting_and_filtering/reports/team" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/reporting_and_filtering/season-window/club
Auth requiredreporting_and_filtering.reporting.view

The app default, the club override (null = inherit), the resolved effective window, and the previous season

Requires features: reporting_and_filtering.reporting.view

Responses

200Club season window
Content-Type: application/json
{
  "appDefault": {
    "start": "string",
    "end": "string"
  },
  "override": null,
  "effective": {
    "from": "string",
    "to": "string",
    "source": "app"
  },
  "previous": {
    "from": "string",
    "to": "string"
  }
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/reporting_and_filtering/season-window/club" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/reporting_and_filtering/season-window/club
Auth requiredreporting_and_filtering.reporting.config

Set (one concrete season instance as full dates, stored as recurring MM-DD) or clear (both null) the club override - club admin only

Requires features: reporting_and_filtering.reporting.config

Responses

200Stored override
Content-Type: application/json
{
  "seasonStart": null,
  "seasonEnd": null
}
422Impossible or half-set window
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/reporting_and_filtering/season-window/club" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/reporting_and_filtering/season-window/team
Auth requiredreporting_and_filtering.reporting.view

The team override (null = inherit), the resolved effective window (team, club, or app source), and the previous season

Requires features: reporting_and_filtering.reporting.view

Responses

200Team season window
Content-Type: application/json
{
  "override": null,
  "effective": {
    "from": "string",
    "to": "string",
    "source": "app"
  },
  "previous": {
    "from": "string",
    "to": "string"
  },
  "teamOrgId": "00000000-0000-4000-8000-000000000000"
}
404Team not visible in the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/reporting_and_filtering/season-window/team" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/reporting_and_filtering/season-window/team
Auth requiredreporting_and_filtering.reporting.config

Set (a concrete season instance stored as recurring MM-DD) or clear (both null, row kept) the team override - team coach/coordinator or club admin

Requires features: reporting_and_filtering.reporting.config

Responses

200Stored override row
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "seasonStart": null,
  "seasonEnd": null
}
404Team not visible in the caller scope
Content-Type: application/json
{
  "error": "string"
}
422Impossible or half-set window
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/reporting_and_filtering/season-window/team" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Player and guardian portal

Showing 18 of 18 endpoints
POST/player_guardian_portal/guardian-links
Auth requiredplayer_guardian_portal.guardian_link.manage

Link a guardian account to a player (one live link per pair)

Requires features: player_guardian_portal.guardian_link.manage

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "guardianCustomerUserId": "00000000-0000-4000-8000-000000000000"
}

Responses

200Created link
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404No such player
Content-Type: application/json
{
  "error": "string"
}
409Already linked
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/guardian-links" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"guardianCustomerUserId\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/player_guardian_portal/onboarding
Auth requiredplayer_guardian_portal.onboarding.manage

Portal account state per in-scope player

Requires features: player_guardian_portal.onboarding.manage

Responses

200Account state
Content-Type: application/json
{
  "items": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "account": null,
      "guardians": [
        {
          "customerUserId": "00000000-0000-4000-8000-000000000000",
          "email": "string",
          "displayName": "string",
          "status": "pending",
          "linkId": "00000000-0000-4000-8000-000000000000"
        }
      ]
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/onboarding" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/player_guardian_portal/onboarding
Auth requiredplayer_guardian_portal.onboarding.manage

Create (or re-invite) a player or guardian portal account

Creates the CustomerUser in the club organization, assigns the seeded player/guardian CustomerRole, links it to the player (guardians through the guardian-link store), and mails a set-credentials link to the portal reset-password page. Re-posting the same email resends without duplicating the account. Requires features: player_guardian_portal.onboarding.manage

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "subject": "player",
  "email": "user@example.com"
}

Responses

200Provisioned account
Content-Type: application/json
{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "subject": "player",
  "customerUserId": "00000000-0000-4000-8000-000000000000",
  "email": "string",
  "isNewAccount": true,
  "inviteEmailSent": true
}
404No such player in scope
Content-Type: application/json
{
  "error": "string"
}
409Already onboarded elsewhere, or the club is not portal-ready
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/onboarding" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"subject\": \"player\",
  \"email\": \"user@example.com\"
}"
GET/player_guardian_portal/portal-access
Auth requiredplayer_guardian_portal.portal_access.manage

Read whether the portal is enabled for this club

Requires features: player_guardian_portal.portal_access.manage

Responses

200Toggle state
Content-Type: application/json
{
  "isEnabled": true
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/portal-access" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/player_guardian_portal/portal-access
Auth requiredplayer_guardian_portal.portal_access.manage

Turn the club portal on or off

Requires features: player_guardian_portal.portal_access.manage

Request body (application/json)

{
  "isEnabled": true
}

Responses

200New toggle state
Content-Type: application/json
{
  "isEnabled": true
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/portal-access" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"isEnabled\": true
}"
POST/player_guardian_portal/portal/attendance

Upsert the one pending attendance record (availability only for a match)

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "status": "present"
}

Responses

200The single record, pending coach acceptance
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "status": "string",
  "confirmationStatus": "string",
  "source": "string"
}
404Player or session outside scope
Content-Type: application/json
{
  "error": "string"
}
409Cancelled session or player-mark precedence
Content-Type: application/json
{
  "error": "string"
}
422Invalid input (an absence needs a reason)
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/portal/attendance" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"status\": \"present\"
}"
POST/player_guardian_portal/portal/deletion-requests

Player at/above legal age, guardian below it; the archival flow fulfils it

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000"
}

Responses

202Request raised
Content-Type: application/json
{
  "ok": true
}
403The other party owns the request at this age
Content-Type: application/json
{
  "error": "string"
}
404Player outside the account scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/portal/deletion-requests" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/player_guardian_portal/portal/matches

Visibility-gated match list of the scoped player team

Responses

200Match list, statistical pieces per club exposure
Content-Type: application/json
{
  "items": [
    {}
  ]
}
404Player outside the account scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/portal/matches" \
  -H "Accept: application/json"
GET/player_guardian_portal/portal/notifications

Own notifications only, newest first

Responses

200Notification list
Content-Type: application/json
{
  "items": [
    {}
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/portal/notifications" \
  -H "Accept: application/json"
GET/player_guardian_portal/portal/players

List the own player (player account) or linked minors (guardian account)

Responses

200Scoped players
Content-Type: application/json
{
  "items": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "relation": "self",
      "firstName": null,
      "lastName": null
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/portal/players" \
  -H "Accept: application/json"
PUT/player_guardian_portal/portal/profile

Preferred positions and dominant foot only; identity stays staff-owned

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "dominantFoot": null
}

Responses

200Updated player id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
403Guardian without the manage capability
Content-Type: application/json
{
  "error": "string"
}
404Player outside the account scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/portal/profile" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"dominantFoot\": null
}"
GET/player_guardian_portal/portal/schedule

Roster-derived schedule with the one player own attendance and survey state

Responses

200Schedule window
Content-Type: application/json
{
  "items": [
    {}
  ]
}
404Player outside the account scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/portal/schedule" \
  -H "Accept: application/json"
GET/player_guardian_portal/portal/stats

Derived figures narrowed to the statistics the club exposes

Responses

200Visible statistics only; empty when the club exposes nothing
Content-Type: application/json
{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "visibleStatistics": [
    "string"
  ],
  "stats": {}
}
404Player outside the account scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/portal/stats" \
  -H "Accept: application/json"
POST/player_guardian_portal/portal/surveys

One response per (session, player, survey kind); pre-fills a pending present mark

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "sessionId": "00000000-0000-4000-8000-000000000000"
}

Responses

200Write outcomes plus whether attendance was pre-filled
Content-Type: application/json
{
  "results": [
    {
      "id": "string",
      "templateType": "string",
      "outcome": "string"
    }
  ],
  "prefilledAttendance": true
}
404Player or session outside scope
Content-Type: application/json
{
  "error": "string"
}
409Cancelled session, missing minor consent, or capture mode
Content-Type: application/json
{
  "error": "string"
}
422Invalid answers or empty submission
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/portal/surveys" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/player_guardian_portal/visibility
Auth requiredplayer_guardian_portal.visibility.manage

List the live toggle rows

Requires features: player_guardian_portal.visibility.manage

Responses

200Toggle rows
Content-Type: application/json
{
  "items": [
    {}
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/visibility" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/player_guardian_portal/visibility
Auth requiredplayer_guardian_portal.visibility.manage

Toggle one statistic at club or team scope (never per player)

Requires features: player_guardian_portal.visibility.manage

Request body (application/json)

{
  "scope": "club",
  "statisticKey": "match_result",
  "isVisible": true
}

Responses

200The one live row
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "isVisible": true
}
404Team org not in this club
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/player_guardian_portal/visibility" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"scope\": \"club\",
  \"statisticKey\": \"match_result\",
  \"isVisible\": true
}"

Push notifications

Showing 5 of 5 endpoints
GET/push_notifications/devices

Own registered devices only, newest first

Responses

200Device list
Content-Type: application/json
{
  "items": [
    {}
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/push_notifications/devices" \
  -H "Accept: application/json"
POST/push_notifications/devices

Register (or refresh) one of the caller own devices

Responses

200Registered device id
Content-Type: application/json
{
  "id": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/push_notifications/devices" \
  -H "Accept: application/json"
DELETE/push_notifications/devices

Unregister one of the caller own devices; push to it stops, the inbox is untouched

Responses

200Unregistered
Content-Type: application/json
{
  "ok": true
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/push_notifications/devices" \
  -H "Accept: application/json"
GET/push_notifications/preference

Own effective channel preference; both is the default once a device exists

Responses

200Effective preference
Content-Type: application/json
{
  "channel": "in-app",
  "isDefault": true
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/push_notifications/preference" \
  -H "Accept: application/json"
PUT/push_notifications/preference

Set the caller own channel preference; push-only is not offered

Responses

200Stored preference
Content-Type: application/json
{
  "channel": "in-app",
  "isDefault": true
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/push_notifications/preference" \
  -H "Accept: application/json"

Data archival and retention

Showing 4 of 4 endpoints
GET/data_archival_and_retention/requests
Auth requireddata_archival_and_retention.archival.manage

List recorded deletion requests

The club-scoped audit ledger of request-only deletions: who asked, on what basis, and execution state. Requires features: data_archival_and_retention.archival.manage

Responses

200Deletion requests, newest first
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "playerId": "00000000-0000-4000-8000-000000000000",
      "requestedBy": "player",
      "eligibilityBasis": "self_legal_age",
      "intake": "out_of_band",
      "status": "recorded",
      "requestedAt": "string",
      "executedAt": null
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/data_archival_and_retention/requests" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/data_archival_and_retention/requests
Auth requireddata_archival_and_retention.archival.manage

Record an out-of-band deletion request

Validates the requester against the club legal age (player at/above, else guardian) while the date of birth still exists, then records the request. Execution is a separate call. Requires features: data_archival_and_retention.archival.manage

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000",
  "requestedBy": "player",
  "intake": "out_of_band"
}

Responses

201Recorded request id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Player not found
Content-Type: application/json
{
  "error": "string"
}
422Missing requester, ineligible requester, or anonymized record
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/data_archival_and_retention/requests" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"requestedBy\": \"player\",
  \"intake\": \"out_of_band\"
}"
POST/data_archival_and_retention/requests/execute
Auth requireddata_archival_and_retention.archival.manage

Execute an eligible deletion request (hard-remove cascade)

Hard-removes the player across every per-player domain, profile last, and stamps the ledger. An active player needs a recorded request or an inline eligible requester; an anonymized record purges without a data-subject gate. Requires features: data_archival_and_retention.archival.manage

Request body (application/json)

{
  "playerId": "00000000-0000-4000-8000-000000000000"
}

Responses

200Purged
Content-Type: application/json
{
  "ok": true,
  "alreadyAnonymized": true
}
404Player not found
Content-Type: application/json
{
  "error": "string"
}
422No recorded request or ineligible requester
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/data_archival_and_retention/requests/execute" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"playerId\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/data_archival_and_retention/sweep
Auth requireddata_archival_and_retention.archival.manage

Run the retention sweep for the caller club

Purges anonymized players archived strictly longer ago than the club retention window (null = indefinite, nothing purged) and re-applies the anonymization contract as the event backstop. Requires features: data_archival_and_retention.archival.manage

Responses

200Sweep counts
Content-Type: application/json
{
  "backstopped": 1,
  "purged": 1
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/data_archival_and_retention/sweep" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Attendance

Showing 10 of 10 endpoints
GET/attendance_tracking
Auth requiredattendance_tracking.attendance.view

List attendance records

The attendance history read, scoped to the caller's visible teams. A record FKs the persistent player, so a player's history survives roster changes and transfers; a guest's record carries the host team-org and never shows under the home-team filter. Filters: session, player, team, outcome, guest flag, confirmation. Requires features: attendance_tracking.attendance.view

Responses

200Attendance records
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "sessionId": "00000000-0000-4000-8000-000000000000",
      "playerId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "status": "present",
      "absenceReason": null,
      "source": "staff",
      "isGuest": true,
      "confirmationStatus": "confirmed",
      "markedByUserId": null,
      "markedAt": "string",
      "confirmedByUserId": null,
      "confirmedAt": null
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team or session not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/attendance_tracking" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/attendance_tracking
Auth requiredattendance_tracking.attendance.manage

Record or correct one attendance mark

Upserts the single live record for (session, player): a roster player of the session team at session time, or a consented guest. Rejected when the session is cancelled (409), the player is off the sheet (422), or a guest consent is pending/revoked (409). Requires features: attendance_tracking.attendance.manage

Request body (application/json)

{
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "playerId": "00000000-0000-4000-8000-000000000000",
  "status": "present"
}

Responses

200Upserted record id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled session or unconsented guest
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or off-sheet player
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/attendance_tracking" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"status\": \"present\"
}"
POST/attendance_tracking/accept
Auth requiredattendance_tracking.attendance.manage

Confirm a pending record, optionally overriding the outcome

The phase-2 acceptance surface, shipped now so the record shape is complete: a pending portal self-report becomes final only through the coach, who may edit the outcome while accepting. A pending mark is never final silently. Requires features: attendance_tracking.attendance.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Confirmed record id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Record or session not found
Content-Type: application/json
{
  "error": "string"
}
409Not pending or cancelled session
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/attendance_tracking/accept" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/attendance_tracking/bulk
Auth requiredattendance_tracking.attendance.manage

Record the whole sheet in one pass

The coach's primary write path (feature §5): upserts every mark on (session, player) in one transaction over the session team's roster at session time plus its consented guests. The whole submission is validated before any row is touched. Requires features: attendance_tracking.attendance.manage

Request body (application/json)

{
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "marks": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "status": "present"
    }
  ]
}

Responses

200Upserted record ids
Content-Type: application/json
{
  "ids": [
    "00000000-0000-4000-8000-000000000000"
  ]
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled session or unconsented guest
Content-Type: application/json
{
  "error": "string"
}
422Invalid input, duplicate player, or off-sheet player
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/attendance_tracking/bulk" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"marks\": [
    {
      \"playerId\": \"00000000-0000-4000-8000-000000000000\",
      \"status\": \"present\"
    }
  ]
}"
GET/attendance_tracking/guests
Auth requiredattendance_tracking.attendance.view

List an event's guests

Guests are resolved per event (feature §5a): every row here is one player from another in-club team on this one host event, with the inline consent decision. Requires features: attendance_tracking.attendance.view

Responses

200Event guests
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "sessionId": "00000000-0000-4000-8000-000000000000",
      "hostTeamOrgId": "00000000-0000-4000-8000-000000000000",
      "guestPlayerId": "00000000-0000-4000-8000-000000000000",
      "homeTeamOrgId": "00000000-0000-4000-8000-000000000000",
      "addedByUserId": "string",
      "consentStatus": "not_required",
      "consentByUserId": null,
      "consentAt": null
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/attendance_tracking/guests" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/attendance_tracking/guests
Auth requiredattendance_tracking.guest.manage

Add an in-club guest to the event

The guest must hold a live membership on the named home team - another team in the SAME club - and not on the host roster. A club-admin or host-coordinator add lands not_required; a coach add lands pending until the guest's home-team coach grants consent. Requires features: attendance_tracking.guest.manage

Request body (application/json)

{
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "guestPlayerId": "00000000-0000-4000-8000-000000000000",
  "homeTeamOrgId": "00000000-0000-4000-8000-000000000000"
}

Responses

201Created guest id and its consent status
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "consentStatus": "not_required"
}
404Session or player not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled session or duplicate guest
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or ineligible guest
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/attendance_tracking/guests" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"guestPlayerId\": \"00000000-0000-4000-8000-000000000000\",
  \"homeTeamOrgId\": \"00000000-0000-4000-8000-000000000000\"
}"
DELETE/attendance_tracking/guests
Auth requiredattendance_tracking.guest.manage

Remove a guest from the event

Soft-deletes the per-event guest row; an already-recorded attendance mark stays as history. Requires features: attendance_tracking.guest.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed
Content-Type: application/json
{
  "ok": true
}
404Guest not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/attendance_tracking/guests" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/attendance_tracking/guests/consent
Auth requiredattendance_tracking.guest.consent

Grant the consent

Only a coach/coordinator of the guest's HOME team (or the club admin) decides; the decision is per event and never reused. Granting makes the guest markable on the host sheet. Requires features: attendance_tracking.guest.consent

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Granted
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "consentStatus": "not_required"
}
404Guest not found
Content-Type: application/json
{
  "error": "string"
}
409No consent required
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/attendance_tracking/guests/consent" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/attendance_tracking/sheet
Auth requiredattendance_tracking.attendance.view

Read the roster-and-guests mark sheet for one session

The derived sheet the coach fills, in one request: the session team's memberships active at session time UNION the event's unrevoked guests, joined to the decrypted player identity and each player's current record. A cancelled session returns the sheet read-only (no row markable); a pending-consent guest is listed but blocked. Requires features: attendance_tracking.attendance.view

Responses

200The mark sheet
Content-Type: application/json
{
  "session": {
    "id": "00000000-0000-4000-8000-000000000000",
    "teamOrgId": "00000000-0000-4000-8000-000000000000",
    "eventType": "string",
    "status": "string",
    "startsAt": "string"
  },
  "rows": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "firstName": null,
      "lastName": null,
      "isGuest": true,
      "consentStatus": null,
      "markable": true,
      "record": null
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/attendance_tracking/sheet" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Auth

Showing 1 of 1 endpoints
GET/auth/users/consents
Auth requiredauth.users.edit

List user consents

Returns all consent records for a given user, with integrity verification status. Requires features: auth.users.edit

Parameters

NameInRequiredSchemaDescription
userIdqueryYesany—

Responses

200Consent list returned
Content-Type: application/json
"string"

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/auth/users/consents?userId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Calendar

Showing 3 of 3 endpoints
GET/calendar/club
Auth requiredcalendar.club_calendar.view

Read the whole club's fixtures across all teams

The view-only club calendar (PRD 5.3.2): a reduced, read-only fixture projection (who plays where and against whom) across every team in the caller tenant, regardless of the caller team scope. Never crosses the club boundary and offers no mutation. Requires features: calendar.club_calendar.view

Responses

200Club fixtures
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "teamName": null,
      "eventType": "string",
      "status": "string",
      "startsAt": "string",
      "opponentName": null,
      "homeAway": null,
      "locationName": null,
      "locationAddress": null
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/calendar/club" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/calendar/context
Auth requiredsessions.session.view

Resolve the caller's default calendar view

Derives the role default view from the caller staff assignments (coordinator opens on week, coach on month, the assignment-less club admin on week) and whether the week view is single-team (a coach). UI state only - it grants and restricts nothing. Requires features: sessions.session.view

Responses

200Calendar context
Content-Type: application/json
{
  "defaultView": "grid",
  "singleTeamWeek": true
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/calendar/context" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/calendar/export
Auth requiredsessions.session.view

Export the caller's in-scope schedule as an iCalendar file

One-way app-to-external sync (PRD 5.3.4): a read-only ICS document of the caller in-scope fixtures - one team via teamOrgId or the whole in-scope set - carrying title, start, meeting time, duration, location, and note per event; a cancelled event is emitted as STATUS:CANCELLED. Nothing is ever read back. Requires features: sessions.session.view

Responses

200text/calendar document
Content-Type: application/json
"string"
404Team out of scope
Content-Type: application/json
{
  "error": "string"
}
422Invalid scope selection
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/calendar/export" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Match tagging

Showing 8 of 8 endpoints
GET/clubs/sessions/{id}/match-tags
Auth requiredclubs.match_tagging.view

Read the six-metric team counts

A COUNT over the shared timeline, zero-filled over the six core metrics plus any operator-added ones - the per-match source the 5.2.2 team-statistics view reads. Nothing opponent-side is included; a cancelled match stays readable but its counts enter no statistics. Requires features: clubs.match_tagging.view

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Zero-filled tag counts
Content-Type: application/json
{
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "counts": {
    "key": 1
  }
}
404Match not found
Content-Type: application/json
{
  "error": "string"
}
422Not a match
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/clubs/sessions/:id/match-tags" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/clubs/sessions/{id}/match-tags
Auth requiredclubs.match_tagging.manage

Set one metric count directly

Reconciles the timeline to the target figure - no raw totals table exists, so missing taps are appended team-level and surplus taps are soft-deleted newest-first. A count below zero is unrepresentable. Requires features: clubs.match_tagging.manage

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "metric": "string",
  "count": 1
}

Responses

200The reconciled metric and count
Content-Type: application/json
{
  "metric": "string",
  "count": 1
}
404Match not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled match
Content-Type: application/json
{
  "error": "string"
}
422Not a match or unknown metric
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/clubs/sessions/:id/match-tags" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"metric\": \"string\",
  \"count\": 1
}"
GET/clubs/sessions/{id}/match-timeline
Auth requiredclubs.match_tagging.view

Read the match timeline and the on-pitch list

The ordered shared timeline plus the derived on-pitch list (starting XI + subs) the home player picker draws on. A training session offers no tagging sheet. Requires features: clubs.match_tagging.view

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Timeline view
Content-Type: application/json
{
  "session": {
    "id": "00000000-0000-4000-8000-000000000000",
    "teamOrgId": "00000000-0000-4000-8000-000000000000",
    "eventType": "string",
    "status": "string",
    "taggable": true
  },
  "events": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "sessionId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "minute": 1,
      "eventType": "string",
      "side": "home",
      "playerId": null,
      "recordedByUserId": null,
      "recordedAt": "string"
    }
  ],
  "onPitchPlayerIds": [
    "string"
  ]
}
404Match not found
Content-Type: application/json
{
  "error": "string"
}
422Not a match
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/clubs/sessions/:id/match-timeline" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/clubs/sessions/{id}/match-timeline
Auth requiredclubs.match_tagging.manage

Record one live tap

The companion hot path - one small append per tap. A home per-player tap names an on-pitch player; an opponent tag-metric tap is live display only and persists nothing (id: null); a cancelled match takes no taps. Requires features: clubs.match_tagging.manage

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "minute": 1,
  "eventType": "string",
  "side": "home"
}

Responses

200Appended event id, or null for a display-only opponent tap
Content-Type: application/json
{
  "id": null
}
404Match not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled match
Content-Type: application/json
{
  "error": "string"
}
422Not a match, unknown event key, or invalid attribution
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/clubs/sessions/:id/match-timeline" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"minute\": 1,
  \"eventType\": \"string\",
  \"side\": \"home\"
}"
DELETE/clubs/sessions/{id}/match-timeline
Auth requiredclubs.match_tagging.manage

Undo a mistap

Soft-deletes one tapped row; the derived counts reflect the change on the next read. Requires features: clubs.match_tagging.manage

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed event id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Event not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled match
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/clubs/sessions/:id/match-timeline" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/clubs/tag-metrics
Auth requiredclubs.match_tagging.view

List the effective tag metrics

The six core metrics (shots on/off/saved/blocked, tackles, fouls) plus every active operator-added metric - the set the companion offers to tap and the rollup zero-fills. Platform-level, shared by every club. Requires features: clubs.match_tagging.view

Responses

200Effective metrics
Content-Type: application/json
{
  "items": [
    {
      "id": null,
      "metric": "string",
      "label": null,
      "core": true
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/clubs/tag-metrics" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/clubs/tag-metrics
Auth requiredclubs.tag_metrics.manage

Add an operator metric

App-operator capability only - club members cannot extend the set. The key must fit the shared timeline column and not collide with a shipped event type. Requires features: clubs.tag_metrics.manage

Request body (application/json)

{
  "metric": "string",
  "label": "string"
}

Responses

200Created catalogue id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
409Duplicate metric key
Content-Type: application/json
{
  "error": "string"
}
422Invalid or shipped key
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/clubs/tag-metrics" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"metric\": \"string\",
  \"label\": \"string\"
}"
DELETE/clubs/tag-metrics
Auth requiredclubs.tag_metrics.manage

Remove an operator metric

Soft-deletes the catalogue row; already tagged rows keep resolving. Requires features: clubs.tag_metrics.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed catalogue id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Metric not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/clubs/tag-metrics" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"

Configs

Showing 8 of 8 endpoints
GET/configs/cache
Auth requiredconfigs.cache.view

Get cache statistics

Returns detailed cache statistics including total entries and breakdown by cache segments. Requires cache service to be available. Requires features: configs.cache.view

Responses

200Cache statistics
Content-Type: application/json
{
  "generatedAt": "string",
  "totalKeys": 1,
  "segments": [
    {
      "segment": "string",
      "resource": null,
      "method": null,
      "path": null,
      "keyCount": 1,
      "keys": [
        "string"
      ]
    }
  ]
}
500Failed to resolve cache stats
Content-Type: application/json
{
  "error": "string"
}
503Cache service unavailable
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/configs/cache" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/configs/cache
Auth requiredconfigs.cache.manage

Purge cache

Purges cache entries. Supports two actions: purgeAll (clears entire cache) or purgeSegment (clears specific segment). Returns updated cache statistics after purge. Requires features: configs.cache.manage

Request body (application/json)

{
  "action": "purgeAll"
}

Responses

200Cache segment cleared successfully
Content-Type: application/json
{
  "action": "purgeSegment",
  "segment": "string",
  "deleted": 1,
  "stats": {
    "generatedAt": "string",
    "totalKeys": 1,
    "segments": [
      {
        "segment": "string",
        "resource": null,
        "method": null,
        "path": null,
        "keyCount": 1,
        "keys": [
          "string"
        ]
      }
    ]
  }
}
400Invalid request - missing segment identifier for purgeSegment action
Content-Type: application/json
{
  "error": "string"
}
500Failed to purge cache
Content-Type: application/json
{
  "error": "string"
}
503Cache service unavailable
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/configs/cache" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"action\": \"purgeAll\"
}"
GET/configs/module-telemetry
Auth requiredconfigs.system_status.view

Get module resource usage telemetry

Returns in-process module resource attribution for API routes, event subscribers, and queue workers. Requires features: configs.system_status.view

Responses

200Module resource usage report
Content-Type: application/json
{
  "generatedAt": "string",
  "startedAt": "string",
  "enabled": true,
  "bucketIntervalMs": 1,
  "totals": {
    "modules": 1,
    "operations": 1,
    "calls": 1,
    "errors": 1,
    "totalDurationMs": 1,
    "totalCpuMs": 1,
    "positiveHeapDeltaBytes": 1,
    "positiveRssDeltaBytes": 1
  },
  "thresholds": {
    "p95DurationMs": 1,
    "cpuMs": 1,
    "positiveHeapDeltaBytes": 1,
    "positiveRssDeltaBytes": 1,
    "errors": 1
  },
  "modules": [
    {
      "moduleId": "string",
      "calls": 1,
      "errors": 1,
      "totalDurationMs": 1,
      "p95DurationMs": 1,
      "totalCpuMs": 1,
      "positiveHeapDeltaBytes": 1,
      "positiveRssDeltaBytes": 1,
      "surfaces": [
        {
          "surface": "api",
          "calls": 1,
          "errors": 1,
          "totalDurationMs": 1,
          "p95DurationMs": 1,
          "totalCpuMs": 1,
          "positiveHeapDeltaBytes": 1,
          "positiveRssDeltaBytes": 1
        }
      ],
      "topOperations": [
        {
          "moduleId": "string",
          "surface": "api",
          "operation": "string",
          "resourceId": null,
          "calls": 1,
          "errors": 1,
          "totalDurationMs": 1,
          "maxDurationMs": 1,
          "p95DurationMs": 1,
          "totalCpuUserMs": 1,
          "totalCpuSystemMs": 1,
          "maxCpuMs": 1,
          "totalHeapDeltaBytes": 1,
          "positiveHeapDeltaBytes": 1,
          "maxHeapDeltaBytes": 1,
          "totalRssDeltaBytes": 1,
          "positiveRssDeltaBytes": 1,
          "maxRssDeltaBytes": 1,
          "firstSeenAt": "string",
          "lastSeenAt": "string"
        }
      ],
      "candidateReasons": [
        "string"
      ]
    }
  ],
  "candidates": [
    {
      "moduleId": "string",
      "calls": 1,
      "errors": 1,
      "totalDurationMs": 1,
      "p95DurationMs": 1,
      "totalCpuMs": 1,
      "positiveHeapDeltaBytes": 1,
      "positiveRssDeltaBytes": 1,
      "surfaces": [
        {
          "surface": "api",
          "calls": 1,
          "errors": 1,
          "totalDurationMs": 1,
          "p95DurationMs": 1,
          "totalCpuMs": 1,
          "positiveHeapDeltaBytes": 1,
          "positiveRssDeltaBytes": 1
        }
      ],
      "topOperations": [
        {
          "moduleId": "string",
          "surface": "api",
          "operation": "string",
          "resourceId": null,
          "calls": 1,
          "errors": 1,
          "totalDurationMs": 1,
          "maxDurationMs": 1,
          "p95DurationMs": 1,
          "totalCpuUserMs": 1,
          "totalCpuSystemMs": 1,
          "maxCpuMs": 1,
          "totalHeapDeltaBytes": 1,
          "positiveHeapDeltaBytes": 1,
          "maxHeapDeltaBytes": 1,
          "totalRssDeltaBytes": 1,
          "positiveRssDeltaBytes": 1,
          "maxRssDeltaBytes": 1,
          "firstSeenAt": "string",
          "lastSeenAt": "string"
        }
      ],
      "candidateReasons": [
        "string"
      ]
    }
  ],
  "buckets": [
    {
      "bucketStart": "string",
      "bucketEnd": "string",
      "bucketIntervalMs": 1,
      "stage": "startup",
      "partial": true,
      "totals": {
        "modules": 1,
        "calls": 1,
        "errors": 1,
        "totalDurationMs": 1,
        "totalCpuMs": 1,
        "positiveHeapDeltaBytes": 1,
        "positiveRssDeltaBytes": 1
      },
      "modules": [
        {
          "moduleId": "string",
          "calls": 1,
          "errors": 1,
          "totalDurationMs": 1,
          "p95DurationMs": 1,
          "totalCpuMs": 1,
          "positiveHeapDeltaBytes": 1,
          "positiveRssDeltaBytes": 1,
          "surfaces": [
            {
              "surface": "api",
              "calls": 1,
              "errors": 1,
              "totalDurationMs": 1,
              "p95DurationMs": 1,
              "totalCpuMs": 1,
              "positiveHeapDeltaBytes": 1,
              "positiveRssDeltaBytes": 1
            }
          ],
          "topOperations": [
            {
              "moduleId": "string",
              "surface": "api",
              "operation": "string",
              "resourceId": null,
              "calls": 1,
              "errors": 1,
              "totalDurationMs": 1,
              "maxDurationMs": 1,
              "p95DurationMs": 1,
              "totalCpuUserMs": 1,
              "totalCpuSystemMs": 1,
              "maxCpuMs": 1,
              "totalHeapDeltaBytes": 1,
              "positiveHeapDeltaBytes": 1,
              "maxHeapDeltaBytes": 1,
              "totalRssDeltaBytes": 1,
              "positiveRssDeltaBytes": 1,
              "maxRssDeltaBytes": 1,
              "firstSeenAt": "string",
              "lastSeenAt": "string"
            }
          ],
          "candidateReasons": [
            "string"
          ]
        }
      ]
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/configs/module-telemetry" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
DELETE/configs/module-telemetry
Auth requiredconfigs.manage

Clear module telemetry data

Development-only endpoint that clears in-memory module telemetry and local process telemetry files. Requires features: configs.manage

Responses

200Module telemetry cleared
Content-Type: application/json
{
  "cleared": true
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/configs/module-telemetry" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/configs/system-status
Auth requiredconfigs.system_status.view

Get system health status

Returns comprehensive system health information including environment details, version, resource usage, and service connectivity status. Requires features: configs.system_status.view

Responses

200System status snapshot
Content-Type: application/json
{
  "generatedAt": "string",
  "runtimeMode": "development",
  "categories": [
    {
      "key": "profiling",
      "labelKey": "string",
      "descriptionKey": null,
      "items": [
        {
          "key": "string",
          "category": "profiling",
          "kind": "boolean",
          "labelKey": "string",
          "descriptionKey": "string",
          "docUrl": null,
          "defaultValue": null,
          "state": "enabled",
          "value": null,
          "normalizedValue": null
        }
      ]
    }
  ]
}
500Failed to load system status
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/configs/system-status" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/configs/system-status
Auth requiredconfigs.manage

Clear system cache

Purges the entire cache for the current tenant. Useful for troubleshooting or forcing fresh data loading. Requires features: configs.manage

Responses

200Cache cleared successfully
Content-Type: application/json
{
  "cleared": true
}
500Failed to purge cache
Content-Type: application/json
{
  "error": "string"
}
503Cache service unavailable
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/configs/system-status" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/configs/upgrade-actions
Auth requiredconfigs.manage

List pending upgrade actions

Returns a list of pending upgrade actions for the current version. These are one-time setup tasks that need to be executed after upgrading to a new version. Requires organization and tenant context. Requires features: configs.manage

Responses

200List of pending upgrade actions
Content-Type: application/json
{
  "version": "string",
  "actions": [
    {
      "id": "string",
      "version": "string",
      "message": "string",
      "ctaLabel": "string",
      "successMessage": "string",
      "loadingLabel": "string"
    }
  ]
}
400Missing organization or tenant context
Content-Type: application/json
{
  "error": "string"
}
500Failed to load upgrade actions
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/configs/upgrade-actions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/configs/upgrade-actions
Auth requiredconfigs.manage

Execute upgrade action

Executes a specific upgrade action by ID. Typically used for one-time setup tasks like seeding example data after version upgrade. Returns execution status and localized success message. Requires features: configs.manage

Request body (application/json)

{
  "actionId": "string"
}

Responses

200Upgrade action executed successfully
Content-Type: application/json
{
  "status": "string",
  "message": "string",
  "version": "string"
}
400Invalid request body or missing context
Content-Type: application/json
{
  "error": "string"
}
500Failed to execute upgrade action
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/configs/upgrade-actions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"actionId\": \"string\"
}"

CustomerAccounts

Showing 8 of 8 endpoints
GET/customer_accounts/admin/domain-mappings
Auth requiredcustomer_accounts.domain.manage

List domain mappings

Returns all custom-domain mappings for the current tenant, optionally filtered by organization. Requires features: customer_accounts.domain.manage

Responses

200OK
Content-Type: application/json
{
  "ok": true,
  "domainMappings": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "hostname": "string",
      "organizationId": "00000000-0000-4000-8000-000000000000",
      "tenantId": "00000000-0000-4000-8000-000000000000",
      "provider": "traefik",
      "status": "pending",
      "verifiedAt": null,
      "lastDnsCheckAt": null,
      "dnsFailureReason": null,
      "tlsFailureReason": null,
      "tlsRetryCount": 1,
      "cnameTarget": null,
      "aRecordTarget": null,
      "createdAt": "string",
      "updatedAt": null
    }
  ],
  "config": {
    "cnameTarget": null,
    "aRecordTarget": null
  }
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/domain-mappings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/customer_accounts/admin/domain-mappings
Auth requiredcustomer_accounts.domain.manage

Register a custom domain

Registers a new custom domain mapping for an organization. Verifies via DNS asynchronously. Requires features: customer_accounts.domain.manage

Request body (application/json)

{
  "hostname": "string",
  "organizationId": "00000000-0000-4000-8000-000000000000"
}

Responses

201Created
Content-Type: application/json
{
  "ok": true,
  "domainMapping": {
    "id": "00000000-0000-4000-8000-000000000000",
    "hostname": "string",
    "organizationId": "00000000-0000-4000-8000-000000000000",
    "tenantId": "00000000-0000-4000-8000-000000000000",
    "provider": "traefik",
    "status": "pending",
    "verifiedAt": null,
    "lastDnsCheckAt": null,
    "dnsFailureReason": null,
    "tlsFailureReason": null,
    "tlsRetryCount": 1,
    "cnameTarget": null,
    "aRecordTarget": null,
    "createdAt": "string",
    "updatedAt": null
  }
}
400Validation error
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
409Conflict
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/domain-mappings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"hostname\": \"string\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\"
}"
DELETE/customer_accounts/admin/domain-mappings
Auth requiredcustomer_accounts.domain.manage

Remove a custom domain

Removes the domain mapping identified by ?id=. Cache and Traefik routing drain within TTL. Requires features: customer_accounts.domain.manage

Responses

200OK
Content-Type: application/json
{
  "ok": true
}
400Bad request
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/domain-mappings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/customer_accounts/admin/domain-mappings/{id}/health-check
Auth requiredcustomer_accounts.domain.manage

Trigger TLS health check

Runs an HTTPS probe to verify Traefik has provisioned a certificate. On success: transitions the mapping to active. Requires features: customer_accounts.domain.manage

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200OK
Content-Type: application/json
{
  "ok": true,
  "domainMapping": {
    "id": "00000000-0000-4000-8000-000000000000",
    "hostname": "string",
    "status": "string",
    "tlsRetryCount": 1,
    "tlsFailureReason": null
  }
}
404Not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/domain-mappings/:id/health-check" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/customer_accounts/admin/domain-mappings/{id}/verify
Auth requiredcustomer_accounts.domain.manage

Trigger DNS verification

Runs DNS verification (CNAME → A → reverse-resolve fallback) for the domain mapping. Returns diagnostics on failure. Requires features: customer_accounts.domain.manage

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200OK — see status
Content-Type: application/json
{
  "ok": true,
  "domainMapping": {
    "id": "00000000-0000-4000-8000-000000000000",
    "hostname": "string",
    "status": "string",
    "verifiedAt": null,
    "lastDnsCheckAt": null,
    "dnsFailureReason": null
  },
  "diagnostics": null
}
404Not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/domain-mappings/:id/verify" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/customer_accounts/domain-check

Verify a hostname is allowed for TLS provisioning

Called by Traefik before issuing a Let's Encrypt certificate. Requires the X-Domain-Check-Secret header to match DOMAIN_CHECK_SECRET.

Responses

200Hostname allowed
Content-Type: application/json
{
  "ok": true
}
403Forbidden
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404Hostname not registered or not yet verified
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
503Server misconfiguration
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/domain-check" \
  -H "Accept: application/json"
GET/customer_accounts/domain-resolve

Single-host resolve for the Node middleware

Internal endpoint used by the portal middleware to populate its in-memory cache. Requires X-Domain-Resolve-Secret header.

Responses

200OK
Content-Type: application/json
{
  "ok": true,
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "orgSlug": null,
  "status": "active"
}
400Bad request
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Forbidden
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404Not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
503Server misconfiguration
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/domain-resolve" \
  -H "Accept: application/json"
GET/customer_accounts/domain-resolve/all

Batch warm-up for the Node middleware

Returns every active domain mapping in a single payload so the middleware can populate its cache on process start. Requires X-Domain-Resolve-Secret header.

Responses

200OK
Content-Type: application/json
{
  "ok": true,
  "domains": [
    {
      "hostname": "string",
      "tenantId": "00000000-0000-4000-8000-000000000000",
      "organizationId": "00000000-0000-4000-8000-000000000000",
      "orgSlug": null,
      "status": "active"
    }
  ]
}
403Forbidden
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
429Too many requests
Content-Type: application/json
{
  "error": "string"
}
503Server misconfiguration
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/domain-resolve/all" \
  -H "Accept: application/json"

Customer Accounts Admin

Showing 15 of 15 endpoints
GET/customer_accounts/admin/roles

List customer roles (admin)

Returns all customer roles for the tenant.

Responses

200Role list
Content-Type: application/json
{
  "ok": true,
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "slug": "string",
      "description": null,
      "isDefault": true,
      "isSystem": true,
      "customerAssignable": true,
      "createdAt": "string",
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1,
  "page": 1
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/roles" \
  -H "Accept: application/json"
POST/customer_accounts/admin/roles

Create customer role (admin)

Creates a new customer role with an empty ACL.

Request body (application/json)

{
  "name": "string",
  "slug": "string"
}

Responses

201Role created
Content-Type: application/json
{
  "ok": true,
  "role": {
    "id": "00000000-0000-4000-8000-000000000000",
    "name": "string",
    "slug": "string",
    "description": null,
    "isDefault": true,
    "isSystem": true,
    "customerAssignable": true,
    "createdAt": "string"
  }
}
400Validation failed
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
409Slug already exists
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/roles" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"slug\": \"string\"
}"
GET/customer_accounts/admin/roles/{id}

Get customer role detail (admin)

Returns full customer role details including ACL features.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Role detail with ACL
Content-Type: application/json
{
  "ok": true,
  "role": {
    "id": "00000000-0000-4000-8000-000000000000",
    "name": "string",
    "slug": "string",
    "description": null,
    "isDefault": true,
    "isSystem": true,
    "customerAssignable": true,
    "createdAt": "string",
    "updatedAt": null,
    "acl": {
      "features": [
        "string"
      ],
      "isPortalAdmin": true
    }
  }
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404Role not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/roles/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json"
PUT/customer_accounts/admin/roles/{id}

Update customer role (admin)

Updates a customer role. System roles are protected from name changes.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{}

Responses

200Role updated
Content-Type: application/json
{
  "ok": true
}
400Validation failed or system role restriction
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404Role not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/roles/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{}"
DELETE/customer_accounts/admin/roles/{id}

Delete customer role (admin)

Soft deletes a customer role and its ACL. The default role (auto-assigned to new portal users) and roles with assigned users cannot be deleted.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Role deleted
Content-Type: application/json
{
  "ok": true
}
400Default role or has assigned users
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404Role not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/roles/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json"
PUT/customer_accounts/admin/roles/{id}/acl

Update customer role ACL (admin)

Updates the ACL (features and portal admin flag) for a customer role. Invalidates RBAC cache after update.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "features": [
    "string"
  ]
}

Responses

200ACL updated
Content-Type: application/json
{
  "ok": true,
  "updatedAt": "string"
}
400Validation failed
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404Role not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
409Stale ACL write (optimistic-lock conflict)
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/roles/00000000-0000-4000-8000-000000000000/acl" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"features\": [
    \"string\"
  ]
}"
GET/customer_accounts/admin/users

List customer users (admin)

Returns a paginated list of customer users with roles. Supports filtering by status, company, role, and search.

Parameters

NameInRequiredSchemaDescription
pagequeryNoany—
pageSizequeryNoany—
statusqueryNoany—
customerEntityIdqueryNoany—
roleIdqueryNoany—
searchqueryNoany—

Responses

200Paginated user list
Content-Type: application/json
{
  "ok": true,
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "email": "string",
      "displayName": "string",
      "emailVerified": true,
      "isActive": true,
      "lockedUntil": null,
      "lastLoginAt": null,
      "customerEntityId": null,
      "personEntityId": null,
      "createdAt": "string",
      "updatedAt": null,
      "roles": [
        {
          "id": "00000000-0000-4000-8000-000000000000",
          "name": "string",
          "slug": "string"
        }
      ]
    }
  ],
  "total": 1,
  "totalPages": 1,
  "page": 1
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/users" \
  -H "Accept: application/json"
POST/customer_accounts/admin/users

Create customer user (admin)

Creates a new customer user directly. Staff-initiated, bypasses signup flow.

Request body (application/json)

{
  "email": "user@example.com",
  "password": "string",
  "displayName": "string"
}

Responses

201User created
Content-Type: application/json
{
  "ok": true,
  "user": {
    "id": "00000000-0000-4000-8000-000000000000",
    "email": "string",
    "displayName": "string"
  }
}
400Validation failed
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
409Email already exists
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/users" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"email\": \"user@example.com\",
  \"password\": \"string\",
  \"displayName\": \"string\"
}"
POST/customer_accounts/admin/users-invite

Invite customer user (admin)

Creates a staff-initiated invitation for a new customer user. The invitedByUserId is set from the staff auth context.

Request body (application/json)

{
  "email": "user@example.com",
  "roleIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}

Responses

201Invitation created
Content-Type: application/json
{
  "ok": true,
  "invitation": {
    "id": "00000000-0000-4000-8000-000000000000",
    "email": "string",
    "expiresAt": "string"
  }
}
400Validation failed
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
429Too many invitation requests
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/users-invite" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"email\": \"user@example.com\",
  \"roleIds\": [
    \"00000000-0000-4000-8000-000000000000\"
  ]
}"
GET/customer_accounts/admin/users/{id}

Get customer user detail (admin)

Returns full customer user details including CRM links, roles, and active session count.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200User detail
Content-Type: application/json
{
  "ok": true,
  "user": {
    "id": "00000000-0000-4000-8000-000000000000",
    "email": "string",
    "displayName": "string",
    "emailVerified": true,
    "isActive": true,
    "lockedUntil": null,
    "lastLoginAt": null,
    "failedLoginAttempts": 1,
    "customerEntityId": null,
    "personEntityId": null,
    "createdAt": "string",
    "updatedAt": null,
    "roles": [
      {
        "id": "00000000-0000-4000-8000-000000000000",
        "name": "string",
        "slug": "string"
      }
    ],
    "activeSessionCount": 1
  }
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404User not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/users/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json"
PUT/customer_accounts/admin/users/{id}

Update customer user (admin)

Updates a customer user. Staff can update status, lock, CRM links, and roles. Role assignment bypasses customer_assignable check.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "lockedUntil": null,
  "personEntityId": null,
  "customerEntityId": null
}

Responses

200User updated
Content-Type: application/json
{
  "ok": true
}
400Validation failed or role not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404User not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/users/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"lockedUntil\": null,
  \"personEntityId\": null,
  \"customerEntityId\": null
}"
DELETE/customer_accounts/admin/users/{id}

Delete customer user (admin)

Soft deletes a customer user and revokes all their active sessions.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200User deleted
Content-Type: application/json
{
  "ok": true
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404User not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/users/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json"
POST/customer_accounts/admin/users/{id}/reset-password

Reset customer user password (admin)

Allows staff to set a new password for a customer user.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "newPassword": "string"
}

Responses

200Password reset
Content-Type: application/json
{
  "ok": true
}
400Validation failed
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404User not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/users/00000000-0000-4000-8000-000000000000/reset-password" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"newPassword\": \"string\"
}"
POST/customer_accounts/admin/users/{id}/send-reset-link

Send password reset link for customer user (admin)

Creates a password reset token for a customer user and returns a reset link URL. The admin must prepend the appropriate portal domain/slug to the relative URL.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Reset link generated
Content-Type: application/json
{
  "ok": true,
  "resetLink": "string"
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404User not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/users/00000000-0000-4000-8000-000000000000/send-reset-link" \
  -H "Accept: application/json"
POST/customer_accounts/admin/users/{id}/verify-email

Verify customer user email (admin)

Allows staff to manually mark a customer user email as verified.

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Email verified
Content-Type: application/json
{
  "ok": true
}
401Not authenticated
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
403Insufficient permissions
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
404User not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/admin/users/00000000-0000-4000-8000-000000000000/verify-email" \
  -H "Accept: application/json"

Customer Authentication

Showing 8 of 8 endpoints
POST/customer_accounts/email/verify

Verify customer email address

Validates the email verification token and marks the email as verified.

Request body (application/json)

{
  "token": "string"
}

Responses

200Email verified
Content-Type: application/json
{
  "ok": true
}
400Invalid or expired token
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/email/verify" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"token\": \"string\"
}"
POST/customer_accounts/invitations/accept

Accept customer invitation

Accepts an invitation, creates the user account, assigns roles, and auto-logs in.

Request body (application/json)

{
  "token": "string",
  "password": "string",
  "displayName": "string"
}

Responses

201Invitation accepted and user created
Content-Type: application/json
{
  "ok": true,
  "user": {
    "id": "00000000-0000-4000-8000-000000000000",
    "email": "user@example.com",
    "displayName": "string",
    "emailVerified": true
  },
  "resolvedFeatures": [
    "string"
  ]
}
400Invalid or expired invitation
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/invitations/accept" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"token\": \"string\",
  \"password\": \"string\",
  \"displayName\": \"string\"
}"
POST/customer_accounts/login

Authenticate customer credentials

Validates customer credentials and issues JWT + session cookies.

Request body (application/json)

{
  "email": "user@example.com",
  "password": "string"
}

Responses

200Login successful
Content-Type: application/json
{
  "ok": true,
  "user": {
    "id": "00000000-0000-4000-8000-000000000000",
    "email": "user@example.com",
    "displayName": "string",
    "emailVerified": true
  },
  "resolvedFeatures": [
    "string"
  ]
}
400Validation failed
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Invalid credentials
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
429Too many login attempts
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/login" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"email\": \"user@example.com\",
  \"password\": \"string\"
}"
POST/customer_accounts/magic-link/request

Request magic link login

Sends a magic link to the customer email. Always returns 200 to prevent enumeration.

Request body (application/json)

{
  "email": "user@example.com"
}

Responses

200Request accepted
Content-Type: application/json
{
  "ok": true
}
429Too many requests
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/magic-link/request" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"email\": \"user@example.com\"
}"
POST/customer_accounts/magic-link/verify

Verify magic link token

Validates the magic link token, auto-verifies email, and creates a session.

Request body (application/json)

{
  "token": "string"
}

Responses

200Login successful
Content-Type: application/json
{
  "ok": true,
  "user": {
    "id": "00000000-0000-4000-8000-000000000000",
    "email": "user@example.com",
    "displayName": "string",
    "emailVerified": true
  },
  "resolvedFeatures": [
    "string"
  ]
}
400Invalid or expired token
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
401Account not found
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/magic-link/verify" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"token\": \"string\"
}"
POST/customer_accounts/password/reset-confirm

Confirm customer password reset

Validates the reset token and sets a new password. Revokes all existing sessions.

Request body (application/json)

{
  "token": "string",
  "password": "string"
}

Responses

200Password reset successful
Content-Type: application/json
{
  "ok": true
}
400Invalid or expired token
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/password/reset-confirm" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"token\": \"string\",
  \"password\": \"string\"
}"
POST/customer_accounts/password/reset-request

Request customer password reset

Initiates a password reset flow. Always returns 200 to prevent email enumeration.

Request body (application/json)

{
  "email": "user@example.com"
}

Responses

200Request accepted
Content-Type: application/json
{
  "ok": true
}
429Too many requests
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/password/reset-request" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"email\": \"user@example.com\"
}"
POST/customer_accounts/signup

Register a new customer account

Accepts a signup request and always returns 202 to prevent account enumeration.

Request body (application/json)

{
  "email": "user@example.com",
  "password": "string",
  "displayName": "string"
}

Responses

202Signup accepted
Content-Type: application/json
{
  "ok": true
}
400Validation failed or invalid request origin
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}
429Too many signup attempts
Content-Type: application/json
{
  "error": "string"
}
500Signup email origin is not configured
Content-Type: application/json
{
  "ok": false,
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/customer_accounts/signup" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"email\": \"user@example.com\",
  \"password\": \"string\",
  \"displayName\": \"string\"
}"

Dashboards

Showing 10 of 10 endpoints
GET/dashboards/layout
Auth requireddashboards.view

Load the current dashboard layout

Returns the saved widget layout together with the widgets the current user is allowed to place. Requires features: dashboards.view

Responses

200Current dashboard layout and available widgets.
Content-Type: application/json
{
  "layout": {
    "items": [
      {
        "id": "00000000-0000-4000-8000-000000000000",
        "widgetId": "string",
        "order": 1
      }
    ]
  },
  "allowedWidgetIds": [
    "string"
  ],
  "canConfigure": true,
  "context": {
    "userId": "00000000-0000-4000-8000-000000000000",
    "tenantId": null,
    "organizationId": null,
    "userName": null,
    "userEmail": null,
    "userLabel": "string"
  },
  "widgets": [
    {
      "id": "string",
      "title": "string",
      "description": null,
      "defaultSize": "sm",
      "defaultEnabled": true,
      "defaultSettings": null,
      "features": [
        "string"
      ],
      "moduleId": "string",
      "icon": null,
      "loaderKey": "string",
      "supportsRefresh": true
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/dashboards/layout" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/dashboards/layout
Auth requireddashboards.configure

Persist dashboard layout changes

Saves the provided widget ordering, sizes, and settings for the current user. Requires features: dashboards.configure

Request body (application/json)

{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "widgetId": "string",
      "order": 1
    }
  ]
}

Responses

200Layout updated successfully.
Content-Type: application/json
{
  "ok": true
}
400Invalid layout payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/dashboards/layout" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"items\": [
    {
      \"id\": \"00000000-0000-4000-8000-000000000000\",
      \"widgetId\": \"string\",
      \"order\": 1
    }
  ]
}"
PATCH/dashboards/layout/{itemId}
Auth requireddashboards.configure

Update a dashboard layout item

Adjusts the size or settings for a single widget within the dashboard layout. Requires features: dashboards.configure

Parameters

NameInRequiredSchemaDescription
itemIdpathYesany—

Request body (application/json)

{}

Responses

200Layout item updated.
Content-Type: application/json
{
  "ok": true
}
400Invalid payload or missing item id
Content-Type: application/json
{
  "error": "string"
}
404Item not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PATCH "https://trainmaxr.pboj.smallhost.pl/api/dashboards/layout/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
GET/dashboards/roles/widgets
Auth requireddashboards.admin.assign-widgets

Fetch widget assignments for a role

Returns the widgets explicitly assigned to the given role together with the evaluation scope. Requires features: dashboards.admin.assign-widgets

Parameters

NameInRequiredSchemaDescription
roleIdqueryYesany—
tenantIdqueryNoany—
organizationIdqueryNoany—

Responses

200Current widget configuration for the role.
Content-Type: application/json
{
  "widgetIds": [
    "string"
  ],
  "hasCustom": true,
  "scope": {
    "tenantId": null,
    "organizationId": null
  }
}
400Missing role identifier
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/dashboards/roles/widgets?roleId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/dashboards/roles/widgets
Auth requireddashboards.admin.assign-widgets

Update widgets assigned to a role

Persists the widget list for a role within the provided tenant and organization scope. Requires features: dashboards.admin.assign-widgets

Request body (application/json)

{
  "roleId": "00000000-0000-4000-8000-000000000000",
  "widgetIds": [
    "string"
  ]
}

Responses

200Widgets updated successfully.
Content-Type: application/json
{
  "ok": true,
  "widgetIds": [
    "string"
  ]
}
400Invalid payload or unknown widgets
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/dashboards/roles/widgets" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"roleId\": \"00000000-0000-4000-8000-000000000000\",
  \"widgetIds\": [
    \"string\"
  ]
}"
GET/dashboards/users/widgets
Auth requireddashboards.admin.assign-widgets

Read widget overrides for a user

Returns the widgets inherited and explicitly configured for the requested user within the current scope. Requires features: dashboards.admin.assign-widgets

Parameters

NameInRequiredSchemaDescription
userIdqueryYesany—
tenantIdqueryNoany—
organizationIdqueryNoany—

Responses

200Widget settings for the user.
Content-Type: application/json
{
  "mode": "inherit",
  "widgetIds": [
    "string"
  ],
  "hasCustom": true,
  "effectiveWidgetIds": [
    "string"
  ],
  "scope": {
    "tenantId": null,
    "organizationId": null
  }
}
400Missing user identifier
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/dashboards/users/widgets?userId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/dashboards/users/widgets
Auth requireddashboards.admin.assign-widgets

Update user-specific dashboard widgets

Sets the widget override mode and allowed widgets for a user. Passing `mode: inherit` clears overrides. Requires features: dashboards.admin.assign-widgets

Request body (application/json)

{
  "userId": "00000000-0000-4000-8000-000000000000",
  "mode": "inherit",
  "widgetIds": [
    "string"
  ]
}

Responses

200Overrides saved.
Content-Type: application/json
{
  "ok": true,
  "mode": "inherit",
  "widgetIds": [
    "string"
  ]
}
400Invalid payload or unknown widgets
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/dashboards/users/widgets" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"userId\": \"00000000-0000-4000-8000-000000000000\",
  \"mode\": \"inherit\",
  \"widgetIds\": [
    \"string\"
  ]
}"
GET/dashboards/widgets/catalog
Auth requireddashboards.admin.assign-widgets

List available dashboard widgets

Returns the catalog of widgets that modules expose, including defaults and feature requirements. Requires features: dashboards.admin.assign-widgets

Responses

200Widgets available for assignment.
Content-Type: application/json
{
  "items": [
    {
      "id": "string",
      "title": "string",
      "description": null,
      "defaultSize": "sm",
      "defaultEnabled": true,
      "defaultSettings": null,
      "features": [
        "string"
      ],
      "moduleId": "string",
      "icon": null,
      "loaderKey": "string",
      "supportsRefresh": true
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/dashboards/widgets/catalog" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/dashboards/widgets/data
Auth requiredanalytics.view

Fetch aggregated data for dashboard widgets

Executes an aggregation query against the specified entity type and returns the result. Supports date range filtering, grouping, and period-over-period comparison. Requires features: analytics.view

Request body (application/json)

{
  "entityType": "string",
  "metric": {
    "field": "string",
    "aggregate": "count"
  }
}

Responses

200Aggregated data for the widget.
Content-Type: application/json
{
  "value": null,
  "data": [
    {
      "value": null
    }
  ],
  "metadata": {
    "fetchedAt": "string",
    "recordCount": 1
  }
}
400Invalid request payload
Content-Type: application/json
{
  "error": "string"
}
500Internal server error
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/dashboards/widgets/data" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityType\": \"string\",
  \"metric\": {
    \"field\": \"string\",
    \"aggregate\": \"count\"
  }
}"
POST/dashboards/widgets/data/batch
Auth requiredanalytics.view

Fetch aggregated data for multiple dashboard widgets in one request

Resolves a batch of widget data requests with a single authentication, RBAC, organization-scope, and database-context setup. Each request is keyed by an opaque widget id and resolved independently, so a failure in one widget does not fail the batch. Requires features: analytics.view

Request body (application/json)

{
  "requests": [
    {
      "id": "string",
      "request": {
        "entityType": "string",
        "metric": {
          "field": "string",
          "aggregate": "count"
        }
      }
    }
  ]
}

Responses

200Per-widget aggregation results keyed by request id.
Content-Type: application/json
{
  "results": [
    {
      "id": "string",
      "ok": true,
      "data": {
        "value": null,
        "data": [
          {
            "value": null
          }
        ],
        "metadata": {
          "fetchedAt": "string",
          "recordCount": 1
        }
      }
    }
  ]
}
400Invalid request payload
Content-Type: application/json
{
  "error": "string"
}
500Internal server error
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/dashboards/widgets/data/batch" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"requests\": [
    {
      \"id\": \"string\",
      \"request\": {
        \"entityType\": \"string\",
        \"metric\": {
          \"field\": \"string\",
          \"aggregate\": \"count\"
        }
      }
    }
  ]
}"

Directory

Showing 11 of 11 endpoints
GET/directory/organization-branding
Auth requireddirectory.organizations.view

Read sidebar branding for the selected organization

Returns the logo URL used by the backend sidebar for the currently selected organization. Requires features: directory.organizations.view

Responses

200Organization branding
Content-Type: application/json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "organizationName": "string",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "logoUrl": null,
  "updatedAt": null
}
400A concrete organization scope is required
Content-Type: application/json
{
  "error": "string"
}
404Organization not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/directory/organization-branding" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/directory/organization-branding
Auth requireddirectory.organizations.manage

Update sidebar branding for the selected organization

Stores an external image URL or an internal attachment image URL as the selected organization logo. Requires features: directory.organizations.manage

Request body (application/json)

{
  "logoUrl": null
}

Responses

200Updated organization branding
Content-Type: application/json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "organizationName": "string",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "logoUrl": null,
  "updatedAt": null
}
400Save failed
Content-Type: application/json
{
  "error": "string"
}
409Organization branding changed since it was loaded
Content-Type: application/json
{
  "error": "string"
}
422Invalid logo URL
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/directory/organization-branding" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"logoUrl\": null
}"
GET/directory/organization-switcher
Auth required

Load organization switcher menu

Returns the hierarchical menu of organizations the current user may switch to within the active tenant.

Responses

200Organization switcher payload.
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "depth": 1,
      "selectable": true,
      "children": []
    }
  ],
  "selectedId": null,
  "canManage": true,
  "canViewAllOrganizations": true,
  "tenantId": null,
  "tenants": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "isActive": true
    }
  ],
  "isSuperAdmin": true
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/directory/organization-switcher" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/directory/organizations
Auth requireddirectory.organizations.view

List organizations

Returns organizations using options, tree, or paginated manage view depending on the `view` parameter. Requires features: directory.organizations.view

Parameters

NameInRequiredSchemaDescription
pagequeryNoany—
pageSizequeryNoany—
searchqueryNoany—
viewqueryNoany—
idsqueryNoany—
tenantIdqueryNoany—
includeInactivequeryNoany—
statusqueryNoany—

Responses

200Organization data for the requested view.
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "parentId": null,
      "parentName": null,
      "tenantId": null,
      "tenantName": null,
      "rootId": null,
      "treePath": null
    }
  ]
}
400Invalid query or tenant scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/directory/organizations?page=1&pageSize=50&view=options" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/directory/organizations
Auth requireddirectory.organizations.manage

Create organization

Creates a new organization within a tenant and optionally assigns hierarchy relationships. Requires features: directory.organizations.manage

Request body (application/json)

{
  "name": "string",
  "slug": null,
  "logoUrl": null,
  "parentId": null
}

Responses

201Organization created.
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
400Validation failed
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/directory/organizations" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"slug\": null,
  \"logoUrl\": null,
  \"parentId\": null
}"
PUT/directory/organizations
Auth requireddirectory.organizations.manage

Update organization

Updates organization details and hierarchy assignments. Requires features: directory.organizations.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000",
  "slug": null,
  "logoUrl": null,
  "parentId": null
}

Responses

200Organization updated.
Content-Type: application/json
{
  "ok": true
}
400Validation failed
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/directory/organizations" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"slug\": null,
  \"logoUrl\": null,
  \"parentId\": null
}"
DELETE/directory/organizations
Auth requireddirectory.organizations.manage

Delete organization

Soft deletes an organization identified by id. Requires features: directory.organizations.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Organization deleted.
Content-Type: application/json
{
  "ok": true
}
400Validation failed
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/directory/organizations" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/directory/tenants
Auth requireddirectory.tenants.view

List tenants

Returns tenants visible to the current user with optional search and pagination. Requires features: directory.tenants.view

Parameters

NameInRequiredSchemaDescription
idqueryNoany—
pagequeryNoany—
pageSizequeryNoany—
searchqueryNoany—
sortFieldqueryNoany—
sortDirqueryNoany—
isActivequeryNoany—

Responses

200Paged list of tenants.
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "isActive": true,
      "createdAt": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
400Invalid query parameters
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/directory/tenants?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/directory/tenants
Auth requireddirectory.tenants.manage

Create tenant

Creates a new tenant and returns its identifier. Requires features: directory.tenants.manage

Request body (application/json)

{
  "name": "string"
}

Responses

201Tenant created.
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
400Validation failed
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/directory/tenants" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\"
}"
PUT/directory/tenants
Auth requireddirectory.tenants.manage

Update tenant

Updates tenant properties such as name or activation state. Requires features: directory.tenants.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Tenant updated.
Content-Type: application/json
{
  "ok": true
}
400Validation failed
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/directory/tenants" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
DELETE/directory/tenants
Auth requireddirectory.tenants.manage

Delete tenant

Soft deletes the tenant identified by id. Requires features: directory.tenants.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Tenant removed.
Content-Type: application/json
{
  "ok": true
}
400Validation failed
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/directory/tenants" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"

Entities

Showing 19 of 19 endpoints
GET/entities/definitions
Auth required

List active custom field definitions

Returns active custom field definitions for the supplied entity ids, respecting tenant scope and tombstones.

Parameters

NameInRequiredSchemaDescription
entityIdqueryNoany—
entityIdsqueryNoany—
fieldsetqueryNoany—

Responses

200Definition list
Content-Type: application/json
{
  "items": [
    {
      "key": "string",
      "kind": "string",
      "label": "string",
      "entityId": "string"
    }
  ]
}
400Missing entity id
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/entities/definitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/entities/definitions
Auth requiredentities.definitions.manage

Upsert custom field definition

Creates or updates a custom field definition for the current tenant/org scope. Requires features: entities.definitions.manage

Request body (application/json)

{
  "entityId": "string",
  "key": "string",
  "kind": "text"
}

Responses

200Definition saved
Content-Type: application/json
{
  "ok": true,
  "item": {
    "id": "00000000-0000-4000-8000-000000000000",
    "key": "string",
    "kind": "string",
    "configJson": {}
  }
}
400Validation failed
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/entities/definitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"key\": \"string\",
  \"kind\": \"text\"
}"
DELETE/entities/definitions
Auth requiredentities.definitions.manage

Soft delete custom field definition

Marks the specified definition inactive and tombstones it for the current scope. Requires features: entities.definitions.manage

Request body (application/json)

{
  "entityId": "string",
  "key": "string"
}

Responses

200Definition deleted
Content-Type: application/json
{
  "ok": true,
  "version": null
}
400Missing entity id or key
Content-Type: application/json
{
  "error": "string"
}
404Definition not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/entities/definitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"key\": \"string\"
}"
POST/entities/definitions.batch
Auth requiredentities.definitions.manage

Save multiple custom field definitions

Creates or updates multiple definitions for a single entity in one transaction. Requires features: entities.definitions.manage

Request body (application/json)

{
  "entityId": "string",
  "definitions": [
    {
      "key": "string",
      "kind": "text"
    }
  ]
}

Responses

200Definitions saved
Content-Type: application/json
{
  "ok": true,
  "version": null
}
400Validation error
Content-Type: application/json
{
  "error": "string"
}
500Unexpected failure
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/entities/definitions.batch" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"definitions\": [
    {
      \"key\": \"string\",
      \"kind\": \"text\"
    }
  ]
}"
GET/entities/definitions.manage
Auth requiredentities.definitions.manage

Get management snapshot

Returns scoped custom field definitions (including inactive tombstones) for administration interfaces. Requires features: entities.definitions.manage

Parameters

NameInRequiredSchemaDescription
entityIdqueryYesany—

Responses

200Scoped definitions and deleted keys
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "key": "string",
      "kind": "string",
      "configJson": null,
      "organizationId": null,
      "tenantId": null
    }
  ],
  "deletedKeys": [
    "string"
  ],
  "version": null
}
400Missing entity id
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/entities/definitions.manage?entityId=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/entities/definitions.restore
Auth requiredentities.definitions.manage

Restore definition

Reactivates a previously soft-deleted definition within the current tenant/org scope. Requires features: entities.definitions.manage

Request body (application/json)

{
  "entityId": "string",
  "key": "string"
}

Responses

200Definition restored
Content-Type: application/json
{
  "ok": true
}
400Missing entity id or key
Content-Type: application/json
{
  "error": "string"
}
404Definition not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/entities/definitions.restore" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"key\": \"string\"
}"
GET/entities/encryption
Auth requiredentities.definitions.manage

Fetch encryption map

Returns the encrypted field map for the current tenant/organization scope. Requires features: entities.definitions.manage

Parameters

NameInRequiredSchemaDescription
entityIdqueryYesany—

Responses

200Map
Content-Type: application/json
{
  "entityId": "string",
  "fields": [
    {
      "field": "string",
      "hashField": null
    }
  ],
  "updatedAt": null
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/entities/encryption?entityId=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/entities/encryption
Auth requiredentities.definitions.manage

Upsert encryption map

Creates or updates the encryption map for the current tenant/organization scope. Enforces optimistic locking when the caller sends the expected version header. Requires features: entities.definitions.manage

Request body (application/json)

{
  "entityId": "string",
  "tenantId": null,
  "organizationId": null,
  "fields": [
    {
      "field": "string",
      "hashField": null
    }
  ]
}

Responses

200Saved
Content-Type: application/json
{
  "ok": true,
  "updatedAt": null
}
409Optimistic-lock conflict (stale write)
Content-Type: application/json
{
  "error": "string",
  "code": "string",
  "currentUpdatedAt": "string",
  "expectedUpdatedAt": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/entities/encryption" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"tenantId\": null,
  \"organizationId\": null,
  \"fields\": [
    {
      \"field\": \"string\",
      \"hashField\": null
    }
  ]
}"
GET/entities/entities
Auth required

List available entities

Returns generated and custom entities scoped to the caller with field counts per entity.

Responses

200List of entities
Content-Type: application/json
{
  "items": [
    {
      "entityId": "string",
      "source": "code",
      "label": "string",
      "count": 1
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/entities/entities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/entities/entities
Auth requiredentities.definitions.manage

Upsert custom entity

Creates or updates a tenant/org scoped custom entity definition. Requires features: entities.definitions.manage

Request body (application/json)

{
  "entityId": "string",
  "label": "string",
  "description": null,
  "showInSidebar": false,
  "accessRestricted": false
}

Responses

200Entity saved
Content-Type: application/json
{
  "ok": true,
  "item": {
    "id": "00000000-0000-4000-8000-000000000000",
    "entityId": "string",
    "label": "string"
  }
}
400Validation error
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/entities/entities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"label\": \"string\",
  \"description\": null,
  \"showInSidebar\": false,
  \"accessRestricted\": false
}"
DELETE/entities/entities
Auth requiredentities.definitions.manage

Soft delete custom entity

Marks the specified custom entity inactive within the current scope. Requires features: entities.definitions.manage

Request body (application/json)

{
  "entityId": "string"
}

Responses

200Entity deleted
Content-Type: application/json
{
  "ok": true
}
400Missing entity id
Content-Type: application/json
{
  "error": "string"
}
404Entity not found in scope
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/entities/entities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\"
}"
GET/entities/entity-settings
Auth requiredentities.definitions.view

Get custom entity settings

Returns the tenant-scoped default-restricted policy for new custom entities. Requires features: entities.definitions.view

Responses

200Current settings
Content-Type: application/json
{
  "newEntitiesRestrictedByDefault": true
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/entities/entity-settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/entities/entity-settings
Auth requiredentities.definitions.manage

Update custom entity settings

Sets the tenant-scoped default-restricted policy for new custom entities. Requires features: entities.definitions.manage

Request body (application/json)

{
  "newEntitiesRestrictedByDefault": true
}

Responses

200Updated settings
Content-Type: application/json
{
  "ok": true,
  "newEntitiesRestrictedByDefault": true
}
400Invalid payload
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/entities/entity-settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"newEntitiesRestrictedByDefault\": true
}"
GET/entities/records
Auth requiredentities.records.view

List records

Returns paginated records for the supplied entity. Supports custom field filters, exports, and soft-delete toggles. Requires features: entities.records.view

Parameters

NameInRequiredSchemaDescription
entityIdqueryYesany—
pagequeryNoany—
pageSizequeryNoany—
sortFieldqueryNoany—
sortDirqueryNoany—
searchqueryNoany—
searchFieldsqueryNoany—
withDeletedqueryNoany—
formatqueryNoany—
exportScopequeryNoany—
export_scopequeryNoany—
allqueryNoany—
fullqueryNoany—

Responses

200Paginated records
Content-Type: application/json
{
  "items": [
    {}
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
400Missing entity id
Content-Type: application/json
{
  "error": "string"
}
500Unexpected failure
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/entities/records?entityId=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/entities/records
Auth requiredentities.records.manage

Create record

Creates a record for the given entity. When `recordId` is omitted or not a UUID the data engine will generate one automatically. Requires features: entities.records.manage

Request body (application/json)

{
  "entityId": "string",
  "values": {}
}

Responses

200Record created
Content-Type: application/json
{
  "ok": true
}
400Validation failure
Content-Type: application/json
{
  "error": "string"
}
500Unexpected failure
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/entities/records" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"values\": {}
}"
PUT/entities/records
Auth requiredentities.records.manage

Update record

Updates an existing record. If the provided recordId is not a UUID the record will be created instead to support optimistic flows. Requires features: entities.records.manage

Request body (application/json)

{
  "entityId": "string",
  "recordId": "string",
  "values": {}
}

Responses

200Record updated
Content-Type: application/json
{
  "ok": true
}
400Validation failure
Content-Type: application/json
{
  "error": "string"
}
500Unexpected failure
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/entities/records" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"recordId\": \"string\",
  \"values\": {}
}"
DELETE/entities/records
Auth requiredentities.records.manage

Delete record

Soft deletes the specified record within the current tenant/org scope. Requires features: entities.records.manage

Request body (application/json)

{
  "entityId": "string",
  "recordId": "string"
}

Responses

200Record deleted
Content-Type: application/json
{
  "ok": true
}
400Missing entity id or record id
Content-Type: application/json
{
  "error": "string"
}
404Record not found
Content-Type: application/json
{
  "error": "string"
}
500Unexpected failure
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/entities/records" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"recordId\": \"string\"
}"
GET/entities/relations/options
Auth requiredentities.definitions.view

List relation options

Returns up to 200 option entries for populating relation dropdowns, automatically resolving label fields when omitted. An entityId that matches neither an active custom entity nor a registered ORM entity yields an empty option list. Requires features: entities.definitions.view

Parameters

NameInRequiredSchemaDescription
entityIdqueryYesany—
labelFieldqueryNoany—
qqueryNoany—
idsqueryNoany—
routeContextFieldsqueryNoany—

Responses

200Option list
Content-Type: application/json
{
  "items": [
    {
      "value": "string",
      "label": "string"
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/entities/relations/options?entityId=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/entities/sidebar-entities
Auth required

Get sidebar entities

Returns custom entities flagged with `showInSidebar` for the current tenant/org scope.

Responses

200Sidebar entities for navigation
Content-Type: application/json
{
  "items": [
    {
      "entityId": "string",
      "label": "string",
      "href": "string"
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/entities/sidebar-entities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Lineups

Showing 7 of 7 endpoints
GET/lineups_and_positioning/lineups
Auth requiredlineups_and_positioning.lineup.view

List lineups and templates

The lineup and template list, scoped to the caller's visible teams. An event lineup carries its session; a template carries none. Filters: session, team, template-ness. Requires features: lineups_and_positioning.lineup.view

Responses

200Lineups
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "sessionId": null,
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "name": null,
      "formation": null,
      "isTemplate": true,
      "createdByUserId": null
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team or session not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/lineups_and_positioning/lineups" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/lineups_and_positioning/lineups
Auth requiredlineups_and_positioning.lineup.manage

Create an event lineup or a team template

Creates the session's single event lineup (optionally seeded by copying a template's formation and positions - later edits to either side stay independent) or a named team template bound to no event. Rejected when the session already carries its lineup (409), is cancelled (409), or the template belongs to another team (422). Requires features: lineups_and_positioning.lineup.manage

Request body (application/json)

{
  "isTemplate": false
}

Responses

200Created lineup id with any seeded slot ids
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Session, team, or template not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled event or the session already has its lineup
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or a cross-team template
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/lineups_and_positioning/lineups" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"isTemplate\": false
}"
PUT/lineups_and_positioning/lineups
Auth requiredlineups_and_positioning.lineup.manage

Correct a lineup's name or formation

Building, saving, reusing, and correcting are one management capability; a cancelled event lineup is closed (409). Requires features: lineups_and_positioning.lineup.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000",
  "name": null,
  "formation": null
}

Responses

200Corrected lineup id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Lineup not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled event
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or a nameless template
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/lineups_and_positioning/lineups" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": null,
  \"formation\": null
}"
DELETE/lineups_and_positioning/lineups
Auth requiredlineups_and_positioning.lineup.manage

Delete a lineup or template

Soft-deletes the lineup and its slots; the event can then be planned afresh. Requires features: lineups_and_positioning.lineup.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Deleted lineup id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Lineup not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled event
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/lineups_and_positioning/lineups" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/lineups_and_positioning/save-as-template
Auth requiredlineups_and_positioning.lineup.manage

Copy a lineup into a new named template

Copies the lineup's formation and positioned players into a new template on the same team, bound to no event (feature §5). A copy, not a link - later edits to either side stay independent. Requires features: lineups_and_positioning.lineup.manage

Request body (application/json)

{
  "lineupId": "00000000-0000-4000-8000-000000000000",
  "name": "string"
}

Responses

200The new template id and its copied slot ids
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "slotIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}
404Lineup not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or a missing template name
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/lineups_and_positioning/save-as-template" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"lineupId\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\"
}"
GET/lineups_and_positioning/sheet
Auth requiredlineups_and_positioning.lineup.view

Read the builder sheet for one lineup or session

The lineup (or a blank for a session not planned yet), its event, the team's live-membership pool joined to the saved placements, off-roster stragglers marked for adjustment, and the per-player advisory pre-selection flag (red/orange Live fatigue band 5.2.4, expired/overdue health check 5.7.1) composed live from the sibling reads - stored nowhere, re-evaluated on every open, degrading to an absent signal when a sibling read is unavailable, and never blocking selection. No health detail beyond the derived boolean crosses over. Requires features: lineups_and_positioning.lineup.view

Responses

200The builder sheet
Content-Type: application/json
{
  "lineup": null,
  "session": null,
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "rows": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "firstName": null,
      "lastName": null,
      "onRoster": true,
      "slot": null,
      "flags": {}
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Lineup or session not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/lineups_and_positioning/sheet" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/lineups_and_positioning/slots
Auth requiredlineups_and_positioning.lineup.manage

Replace the lineup's slot set in one pass

The coach's primary write path (feature §7): replaces the arrangement - starters with normalized 0-1 pitch coordinates, bench substitutes without, a player at most once. The pool is the lineup team's live-membership roster; a player already positioned on this lineup is grandfathered so a saved off-roster straggler stays correctable. Nothing consults the fatigue or health signal - the flag is advisory, never a gate. Rejected when the event is cancelled (409). Requires features: lineups_and_positioning.lineup.manage

Request body (application/json)

{
  "lineupId": "00000000-0000-4000-8000-000000000000",
  "slots": []
}

Responses

200The lineup id and its saved slot ids
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "slotIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}
404Lineup not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled event
Content-Type: application/json
{
  "error": "string"
}
422Invalid input, duplicate player, or a player outside the roster pool
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/lineups_and_positioning/slots" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"lineupId\": \"00000000-0000-4000-8000-000000000000\",
  \"slots\": []
}"

Match data

Showing 9 of 9 endpoints
GET/match_data_entry/protocol
Auth requiredmatch_data_entry.match_data.view

Read the protocol sheet for one match

The derived sheet the coach fills, in one request: the match team's memberships active at match time joined to the decrypted player identity and each player's current stat line (with the role derived from the recorded substitutions over the stored flag), plus any off-roster guest line, plus the shared timeline events ready to edit (live-tagging pre-fill). A cancelled match returns the sheet closed (recordable: false); a training session is refused (409). Requires features: match_data_entry.match_data.view

Responses

200The protocol sheet
Content-Type: application/json
{
  "session": {
    "id": "00000000-0000-4000-8000-000000000000",
    "teamOrgId": "00000000-0000-4000-8000-000000000000",
    "eventType": "string",
    "status": "string",
    "startsAt": "string",
    "durationMinutes": null,
    "recordable": true
  },
  "rows": [
    {
      "playerId": "00000000-0000-4000-8000-000000000000",
      "firstName": null,
      "lastName": null,
      "onRoster": true,
      "derivedRole": "starter",
      "statLine": null
    }
  ],
  "events": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "sessionId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "minute": 1,
      "eventType": "goal",
      "side": "home",
      "playerId": null,
      "recordedByUserId": null,
      "recordedAt": "string"
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Match not found
Content-Type: application/json
{
  "error": "string"
}
409Not a match
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/match_data_entry/protocol" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/match_data_entry/protocol
Auth requiredmatch_data_entry.match_data.manage

Save the whole protocol in one pass

The coach's primary write path (feature §5) and the single-store endpoint live tagging pre-fills: upserts every stat line, REPLACES the match's timeline events, and reconciles each event-named scorer's goal/assist counts so nothing is double-counted. A substitution is a sub_off + sub_on pair at one minute; an assist requires a team goal at its minute; an empty protocol is a valid save. Requires features: match_data_entry.match_data.manage

Request body (application/json)

{
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "lines": [],
  "events": []
}

Responses

200Saved line and event ids
Content-Type: application/json
{
  "lineIds": [
    "00000000-0000-4000-8000-000000000000"
  ],
  "eventIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}
404Match not found
Content-Type: application/json
{
  "error": "string"
}
409Not a match or cancelled match
Content-Type: application/json
{
  "error": "string"
}
422Invalid input, duplicate player, unpaired substitution, or goal-less assist
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/match_data_entry/protocol" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"lines\": [],
  \"events\": []
}"
GET/match_data_entry/stat-lines
Auth requiredmatch_data_entry.match_data.view

List stat lines

The match-data history read, scoped to the caller's visible teams. A stat line FKs the persistent player, so a player's match history survives roster removal and transfers; a guest's line carries the match team-org. Filters: session, player, team, match role. Requires features: match_data_entry.match_data.view

Responses

200Stat lines
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "sessionId": "00000000-0000-4000-8000-000000000000",
      "playerId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "goals": 1,
      "assists": 1,
      "yellowCards": 1,
      "redCards": 1,
      "minutesPlayed": null,
      "matchRole": "starter",
      "recordedByUserId": null,
      "recordedAt": "string"
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team or match not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/match_data_entry/stat-lines" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/match_data_entry/stat-lines
Auth requiredmatch_data_entry.match_data.manage

Record one stat line

Creates the single live stat line for (match, player). The player is validated to the club only - a guest with no membership on the match team is recorded like any roster player. Rejected when the session is not a match (409), is cancelled (409), or the player already has a line (409). Requires features: match_data_entry.match_data.manage

Request body (application/json)

{
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "playerId": "00000000-0000-4000-8000-000000000000",
  "goals": 0,
  "assists": 0,
  "yellowCards": 0,
  "redCards": 0,
  "matchRole": "unused"
}

Responses

200Created stat line id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Match not found
Content-Type: application/json
{
  "error": "string"
}
409Not a match, cancelled match, or duplicate line
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or out-of-club player
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/match_data_entry/stat-lines" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"goals\": 0,
  \"assists\": 0,
  \"yellowCards\": 0,
  \"redCards\": 0,
  \"matchRole\": \"unused\"
}"
PUT/match_data_entry/stat-lines
Auth requiredmatch_data_entry.match_data.manage

Correct one stat line

Updates the existing live stat line for (match, player) - recording and correcting are the same management capability. Requires features: match_data_entry.match_data.manage

Request body (application/json)

{
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "playerId": "00000000-0000-4000-8000-000000000000",
  "goals": 0,
  "assists": 0,
  "yellowCards": 0,
  "redCards": 0,
  "matchRole": "unused"
}

Responses

200Corrected stat line id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Match or stat line not found
Content-Type: application/json
{
  "error": "string"
}
409Not a match or cancelled match
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/match_data_entry/stat-lines" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"playerId\": \"00000000-0000-4000-8000-000000000000\",
  \"goals\": 0,
  \"assists\": 0,
  \"yellowCards\": 0,
  \"redCards\": 0,
  \"matchRole\": \"unused\"
}"
GET/match_data_entry/timeline-events
Auth requiredmatch_data_entry.match_data.view

List timeline events

The raw minute-stamped inputs the statistics slice derives from - nothing aggregated here. One store for the post-match protocol and the live-tagging companion, so a tagged datum is its protocol record. Filters: session, player, team, event type, side. Requires features: match_data_entry.match_data.view

Responses

200Timeline events
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "sessionId": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "minute": 1,
      "eventType": "goal",
      "side": "home",
      "playerId": null,
      "recordedByUserId": null,
      "recordedAt": "string"
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team or match not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/match_data_entry/timeline-events" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/match_data_entry/timeline-events
Auth requiredmatch_data_entry.match_data.manage

Add one timeline event

Adds a minute-stamped event to the match timeline - no cap on the count. An own goal and an opponent-side or team-metric event attribute to nobody; a home goal, assist, card, or substitution half names its player; an assist requires a recorded team goal at its minute. Disallowed goals are never inserted - only goals that stood become rows. Requires features: match_data_entry.match_data.manage

Request body (application/json)

{
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "minute": 1,
  "eventType": "goal",
  "side": "home"
}

Responses

200Created event id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Match not found
Content-Type: application/json
{
  "error": "string"
}
409Not a match or cancelled match
Content-Type: application/json
{
  "error": "string"
}
422Invalid attribution, out-of-club player, or goal-less assist
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/match_data_entry/timeline-events" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"minute\": 1,
  \"eventType\": \"goal\",
  \"side\": \"home\"
}"
PUT/match_data_entry/timeline-events
Auth requiredmatch_data_entry.match_data.manage

Correct one timeline event

Replaces the facts of one event - the same single record whether it was entered post-match or pre-filled by live tagging. Requires features: match_data_entry.match_data.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000",
  "minute": 1,
  "eventType": "goal",
  "side": "home"
}

Responses

200Corrected event id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Event or match not found
Content-Type: application/json
{
  "error": "string"
}
409Not a match or cancelled match
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/match_data_entry/timeline-events" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"minute\": 1,
  \"eventType\": \"goal\",
  \"side\": \"home\"
}"
DELETE/match_data_entry/timeline-events
Auth requiredmatch_data_entry.match_data.manage

Remove one timeline event

Soft-deletes one event (e.g. a goal later ruled out) - a cancelled match takes no change. Requires features: match_data_entry.match_data.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed event id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Event not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled match
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/match_data_entry/timeline-events" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"

Query Index

Showing 3 of 3 endpoints
POST/query_index/purge
Auth requiredquery_index.purge

Purge query index records

Queues a purge job to remove indexed records for an entity type within the active scope. Requires features: query_index.purge

Request body (application/json)

{
  "entityType": "string"
}

Responses

200Purge job accepted.
Content-Type: application/json
{
  "ok": true
}
400Missing entity type
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/query_index/purge" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityType\": \"string\"
}"
POST/query_index/reindex
Auth requiredquery_index.reindex

Trigger query index rebuild

Queues a reindex job for the specified entity type within the current tenant scope. Requires features: query_index.reindex

Request body (application/json)

{
  "entityType": "string"
}

Responses

200Reindex job accepted.
Content-Type: application/json
{
  "ok": true
}
400Missing entity type
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/query_index/reindex" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityType\": \"string\"
}"
GET/query_index/status
Auth requiredquery_index.status.view

Inspect query index coverage

Returns entity counts comparing base tables with the query index along with the latest job status. Requires features: query_index.status.view

Responses

200Current query index status.
Content-Type: application/json
{
  "items": [
    {
      "entityId": "string",
      "label": "string",
      "baseCount": null,
      "indexCount": null,
      "vectorCount": null,
      "ok": true,
      "job": {
        "status": "idle",
        "startedAt": null,
        "finishedAt": null,
        "heartbeatAt": null,
        "processedCount": null,
        "totalCount": null,
        "scope": null
      }
    }
  ],
  "errors": [
    {
      "id": "string",
      "source": "string",
      "handler": "string",
      "entityType": null,
      "recordId": null,
      "tenantId": null,
      "organizationId": null,
      "message": "string",
      "stack": null,
      "payload": null,
      "occurredAt": "string"
    }
  ],
  "logs": [
    {
      "id": "string",
      "source": "string",
      "handler": "string",
      "level": "info",
      "entityType": null,
      "recordId": null,
      "tenantId": null,
      "organizationId": null,
      "message": "string",
      "details": null,
      "occurredAt": "string"
    }
  ]
}
400Tenant or organization context required
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/query_index/status" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"

Sessions

Showing 12 of 12 endpoints
GET/sessions
Auth requiredsessions.session.view

List a team's sessions and matches

The schedule read: org-scoped to the teams the caller can see, cancelled rows stay visible and marked (cancellation is not deletion). Filters: team, type, status, series, date window. Requires features: sessions.session.view

Responses

200Schedule rows
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "eventType": "training",
      "status": "scheduled",
      "startsAt": "string",
      "meetingTime": null,
      "durationMinutes": null,
      "notes": null,
      "seriesId": null,
      "matchType": null,
      "opponentName": null,
      "homeAway": null,
      "locationName": null,
      "locationAddress": null,
      "plannedPlayerCount": null,
      "reservationId": null,
      "cancelledAt": null,
      "cancelReason": null,
      "cancelNotifiedAt": null
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/sessions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/sessions
Auth requiredsessions.session.manage

Create a training session or match

One event model, two types: a match requires opponent, match type (league/friendly/tournament), home/away, a location, and a duration in minutes (any number, not fixed to 90); a training session takes only an optional duration. Emits sessions.session.created. Requires features: sessions.session.manage

Request body (application/json)

{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "eventType": "training",
  "startsAt": "string"
}

Responses

201Created event id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/sessions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\",
  \"eventType\": \"training\",
  \"startsAt\": \"string\"
}"
PUT/sessions
Auth requiredsessions.session.manage

Edit a session or match

Delegation-aware: a delegated match-coach may edit their one delegated match. A cancelled event is not editable (409). reservationId attaches a match to a same-team facility reservation (null detaches); the reservation then supplies the match location. Emits sessions.session.updated. Requires features: sessions.session.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000",
  "reservationId": null,
  "meetingTime": null,
  "durationMinutes": null,
  "notes": null,
  "locationName": null,
  "locationAddress": null,
  "plannedPlayerCount": null
}

Responses

200Updated event id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}
409Cancelled event
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/sessions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"reservationId\": null,
  \"meetingTime\": null,
  \"durationMinutes\": null,
  \"notes\": null,
  \"locationName\": null,
  \"locationAddress\": null,
  \"plannedPlayerCount\": null
}"
DELETE/sessions
Auth requiredsessions.session.manage

Remove an event created in error

Outright removal, distinct from a cancellation: the row is soft-deleted and disappears from every read. Use the cancel route for a real cancellation. Requires features: sessions.session.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed
Content-Type: application/json
{
  "ok": true
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/sessions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/sessions/{id}
Auth requiredsessions.session.view

Read one session or match

Delegation-aware: resolvable via standing team scope OR an active single-match delegation. Anything else reads as 404. Requires features: sessions.session.view

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200The event
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/sessions/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/sessions/{id}/cancel
Auth requiredsessions.session.manage

Mark an event cancelled (retained, never deleted)

Sets status=cancelled with an optional free-text reason and emits sessions.session.cancelled. The cancelled event is excluded from attendance and every workload/fatigue read. Re-posting updates the reason only while the cancellation notification has not been sent (cancel_notified_at null - inert at launch). Requires features: sessions.session.manage

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{}

Responses

200The cancelled event
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "status": "string",
  "cancelReason": null
}
404Session not found
Content-Type: application/json
{
  "error": "string"
}
409Reason locked after notification
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/sessions/:id/cancel" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
GET/sessions/matches/{id}/delegation
Auth requiredsessions.match.view

List a match's active delegations

Delegation-aware read: standing team scope or the delegate themselves. Requires features: sessions.match.view

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Responses

200Active grants
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "userId": "00000000-0000-4000-8000-000000000000",
      "addedByUserId": "00000000-0000-4000-8000-000000000000",
      "createdAt": "string"
    }
  ]
}
404Match not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/sessions/matches/:id/delegation" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/sessions/matches/{id}/delegation
Auth requiredsessions.match.delegate

Delegate a stand-in coach for this one match

Grants a club coach view/edit access to exactly this match - no staff assignment, no standing team access. Requires standing scope on the match team (a delegate cannot re-delegate). Requires features: sessions.match.delegate

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "userId": "00000000-0000-4000-8000-000000000000"
}

Responses

201The grant id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Match or stand-in not found
Content-Type: application/json
{
  "error": "string"
}
409Already delegated
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or not a match
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/sessions/matches/:id/delegation" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"userId\": \"00000000-0000-4000-8000-000000000000\"
}"
DELETE/sessions/matches/{id}/delegation
Auth requiredsessions.match.delegate

Revoke a delegation

Deactivates the grant; the stand-in loses access to the match immediately. Requires features: sessions.match.delegate

Parameters

NameInRequiredSchemaDescription
idpathYesany—

Request body (application/json)

{
  "userId": "00000000-0000-4000-8000-000000000000"
}

Responses

200Revoked
Content-Type: application/json
{
  "ok": true
}
404Match or delegation not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/sessions/matches/:id/delegation" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"userId\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/sessions/series
Auth requiredsessions.session.manage

Create a weekly training series

Training-only by construction - a match has no recurrence path. The weekly pattern (weekdays + time, bounded by an end date or an occurrence count) is materialized into individual session rows in one transaction, emitting sessions.session.created per occurrence. Requires features: sessions.session.manage

Request body (application/json)

{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "weekdays": [
    "mon"
  ],
  "startDate": "string",
  "timeOfDay": "string",
  "endMode": "until_date"
}

Responses

201The series id with the materialized occurrence ids
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "sessionIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/sessions/series" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\",
  \"weekdays\": [
    \"mon\"
  ],
  \"startDate\": \"string\",
  \"timeOfDay\": \"string\",
  \"endMode\": \"until_date\"
}"
POST/sessions/series/cancel-forward
Auth requiredsessions.session.manage

Cancel the selected occurrence and all future ones

Cancels the pivot plus future scheduled occurrences (past ones stay as recorded), ends the weekly pattern, and emits sessions.session.cancelled per row. A series cancel carries NO series-level reason - the body accepts none. Requires features: sessions.session.manage

Request body (application/json)

{
  "fromSessionId": "00000000-0000-4000-8000-000000000000"
}

Responses

200The series id with the cancelled occurrence ids
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "sessionIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}
404Session or series not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or not a series occurrence
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/sessions/series/cancel-forward" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"fromSessionId\": \"00000000-0000-4000-8000-000000000000\"
}"
POST/sessions/series/edit-forward
Auth requiredsessions.session.manage

Apply a pattern edit to the selected occurrence and all future ones

Pivots on the acted-on occurrence: only scheduled rows starting at or after it change; past occurrences are never touched. A weekday/end-bound change re-materializes the future occurrences. Emits sessions.session.updated per touched row. Requires features: sessions.session.manage

Request body (application/json)

{
  "fromSessionId": "00000000-0000-4000-8000-000000000000",
  "patch": {
    "durationMinutes": null,
    "meetingTime": null,
    "notes": null
  }
}

Responses

200The series id with the touched occurrence ids
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "sessionIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}
404Session or series not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid input or not a series occurrence
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/sessions/series/edit-forward" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"fromSessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"patch\": {
    \"durationMinutes\": null,
    \"meetingTime\": null,
    \"notes\": null
  }
}"

Training content

Showing 20 of 27 endpoints
GET/training_content_tally/categories
Auth requiredtraining_content_tally.content.view

List the club categories (core first), seeding the defaults for a fresh club

Requires features: training_content_tally.content.view

Responses

200Categories
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "code": null,
      "isCore": true,
      "isActive": true
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/categories" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/training_content_tally/categories
Auth requiredtraining_content_tally.catalogue.manage

Add a club-custom category

Requires features: training_content_tally.catalogue.manage

Request body (application/json)

{
  "name": "string"
}

Responses

201Created category id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
422Invalid input
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/categories" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\"
}"
PUT/training_content_tally/categories
Auth requiredtraining_content_tally.catalogue.manage

Rename or retire a club-custom category (core rows are protected)

Requires features: training_content_tally.catalogue.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Updated category id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Category not found
Content-Type: application/json
{
  "error": "string"
}
422Core category protected
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/categories" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
DELETE/training_content_tally/categories
Auth requiredtraining_content_tally.catalogue.manage

Remove an empty club-custom category

Requires features: training_content_tally.catalogue.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed
Content-Type: application/json
{
  "ok": true
}
409Category still holds behaviors
Content-Type: application/json
{
  "error": "string"
}
422Core category protected
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/categories" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/training_content_tally/drift
Auth requiredtraining_content_tally.content.view

A team's per-category drift over the configured window (no target means no alert)

Requires features: training_content_tally.content.view

Responses

200Drift read
Content-Type: application/json
{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "roundKey": "string",
  "thresholdPct": 1,
  "window": null,
  "target": null,
  "actual": [
    {
      "categoryId": "string",
      "count": 1,
      "pct": 1
    }
  ],
  "alerts": [
    {
      "categoryId": "string",
      "targetPct": 1,
      "actualPct": 1,
      "deviationPp": 1,
      "drifted": true
    }
  ],
  "hasDrift": true
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/drift" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/training_content_tally/elements
Auth requiredtraining_content_tally.content.view

List catalogue behaviors, filterable by category and active flag

Requires features: training_content_tally.content.view

Responses

200Behaviors
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "categoryId": "00000000-0000-4000-8000-000000000000",
      "methodId": null,
      "isActive": true
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/elements" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/training_content_tally/elements
Auth requiredtraining_content_tally.catalogue.manage

Add a behavior under exactly one category, optionally linked to a library method

Requires features: training_content_tally.catalogue.manage

Request body (application/json)

{
  "name": "string",
  "categoryId": "00000000-0000-4000-8000-000000000000"
}

Responses

201Created behavior id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
422Invalid category
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/elements" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"categoryId\": \"00000000-0000-4000-8000-000000000000\"
}"
PUT/training_content_tally/elements
Auth requiredtraining_content_tally.catalogue.manage

Rename, recategorize, retire, or relink a behavior

Requires features: training_content_tally.catalogue.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000",
  "methodId": null
}

Responses

200Updated behavior id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Behavior not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid category
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/elements" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"methodId\": null
}"
DELETE/training_content_tally/elements
Auth requiredtraining_content_tally.catalogue.manage

Remove a behavior created in error (a tagged behavior is retired instead)

Requires features: training_content_tally.catalogue.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed
Content-Type: application/json
{
  "ok": true
}
409Behavior already tagged
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/elements" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
GET/training_content_tally/fatigue-correlation
Auth requiredtraining_content_tally.content.view

A team's tagged elements with the correlation deferred until fatigue figures exist

Requires features: training_content_tally.content.view

Responses

200Correlation contract read
Content-Type: application/json
{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "status": "deferred",
  "elements": [
    {
      "elementId": "string",
      "categoryId": "string",
      "tagCount": 1,
      "sessionCount": 1
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/fatigue-correlation" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
GET/training_content_tally/plan-targets
Auth requiredtraining_content_tally.content.view

List a team's targets, filterable by round

Requires features: training_content_tally.content.view

Responses

200Targets
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "roundKey": "string",
      "categoryId": "00000000-0000-4000-8000-000000000000",
      "targetPct": 1
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/plan-targets" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/training_content_tally/plan-targets
Auth requiredtraining_content_tally.content.manage

Replace a (team, round) target set - the percentages must total exactly 100

Requires features: training_content_tally.content.manage

Request body (application/json)

{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "roundKey": "string",
  "targets": [
    {
      "categoryId": "00000000-0000-4000-8000-000000000000",
      "targetPct": 1
    }
  ]
}

Responses

200The stored set
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": "00000000-0000-4000-8000-000000000000",
      "roundKey": "string",
      "categoryId": "00000000-0000-4000-8000-000000000000",
      "targetPct": 1
    }
  ]
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid targets
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/plan-targets" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\",
  \"roundKey\": \"string\",
  \"targets\": [
    {
      \"categoryId\": \"00000000-0000-4000-8000-000000000000\",
      \"targetPct\": 1
    }
  ]
}"
DELETE/training_content_tally/plan-targets
Auth requiredtraining_content_tally.content.manage

Clear a (team, round) target set

Requires features: training_content_tally.content.manage

Request body (application/json)

{
  "teamOrgId": "00000000-0000-4000-8000-000000000000",
  "roundKey": "string"
}

Responses

200Cleared
Content-Type: application/json
{
  "ok": true
}
404Team not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/plan-targets" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"teamOrgId\": \"00000000-0000-4000-8000-000000000000\",
  \"roundKey\": \"string\"
}"
GET/training_content_tally/priorities
Auth requiredtraining_content_tally.content.view

List a team's live priority set or a template's item set

Requires features: training_content_tally.content.view

Responses

200Priorities
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": null,
      "templateId": null,
      "scope": "goal",
      "categoryId": null,
      "elementId": null,
      "priority": 1
    }
  ]
}
400Invalid query
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/priorities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
PUT/training_content_tally/priorities
Auth requiredtraining_content_tally.priority.manage

Replace a team's or template's whole priority set

Requires features: training_content_tally.priority.manage

Request body (application/json)

{
  "entries": [
    {
      "scope": "goal",
      "priority": 1
    }
  ]
}

Responses

200The stored set
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamOrgId": null,
      "templateId": null,
      "scope": "goal",
      "categoryId": null,
      "elementId": null,
      "priority": 1
    }
  ]
}
404Team or template not found
Content-Type: application/json
{
  "error": "string"
}
422Invalid entries
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/priorities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entries\": [
    {
      \"scope\": \"goal\",
      \"priority\": 1
    }
  ]
}"
POST/training_content_tally/priorities/copy
Auth requiredtraining_content_tally.priority.manage

Clone the source set into each target team's live priorities

Requires features: training_content_tally.priority.manage

Request body (application/json)

{
  "toTeamOrgIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}

Responses

200Copied
Content-Type: application/json
{
  "ok": true
}
404Source or target not found
Content-Type: application/json
{
  "error": "string"
}
422Empty source set
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/priorities/copy" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"toTeamOrgIds\": [
    \"00000000-0000-4000-8000-000000000000\"
  ]
}"
GET/training_content_tally/priority-templates
Auth requiredtraining_content_tally.content.view

List the club templates

Requires features: training_content_tally.content.view

Responses

200Templates
Content-Type: application/json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "isActive": true
    }
  ]
}

Example

curl -X GET "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/priority-templates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
POST/training_content_tally/priority-templates
Auth requiredtraining_content_tally.priority.manage

Create a template header

Requires features: training_content_tally.priority.manage

Request body (application/json)

{
  "name": "string"
}

Responses

201Created template id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}

Example

curl -X POST "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/priority-templates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\"
}"
PUT/training_content_tally/priority-templates
Auth requiredtraining_content_tally.priority.manage

Rename or retire a template

Requires features: training_content_tally.priority.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Updated template id
Content-Type: application/json
{
  "id": "00000000-0000-4000-8000-000000000000"
}
404Template not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X PUT "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/priority-templates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
DELETE/training_content_tally/priority-templates
Auth requiredtraining_content_tally.priority.manage

Remove a template with its items

Requires features: training_content_tally.priority.manage

Request body (application/json)

{
  "id": "00000000-0000-4000-8000-000000000000"
}

Responses

200Removed
Content-Type: application/json
{
  "ok": true
}
404Template not found
Content-Type: application/json
{
  "error": "string"
}

Example

curl -X DELETE "https://trainmaxr.pboj.smallhost.pl/api/training_content_tally/priority-templates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"