# API Open Mercato

Version: 0.7.0

Automatycznie wygenerowana definicja OpenAPI dla wszystkich włączonych modułów.

## Servers
- https://demo.hoopi.io/api – Domyślne środowisko

## DELETE `/attachments`

Delete attachment

Removes an uploaded attachment and deletes the stored asset.

**Tags:** Attachments

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Required |

### Responses

**200** – Attachment deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Missing attachment identifier

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Attachment not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/attachments?id=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/attachments`

List attachments for a record

Returns uploaded attachments for the given entity record, ordered by newest first.

**Tags:** Attachments

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| entityId | query | any | Required. Entity identifier that owns the attachments |
| recordId | query | any | Required. Record identifier within the entity |
| page | query | any | Optional |
| pageSize | query | any | Optional |

### Responses

**200** – Attachments found for the record

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "string",
      "url": "string",
      "fileName": "string",
      "fileSize": 1,
      "createdAt": "string",
      "mimeType": null,
      "content": null
    }
  ]
}
```

**400** – Missing entity or record identifiers

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/attachments?entityId=string&recordId=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/attachments`

Upload attachment

Uploads a new attachment using multipart form-data and stores metadata for later retrieval.

**Tags:** Attachments

**Requires authentication.**

### Request Body

Content-Type: `multipart/form-data`

```text
entityId=string
recordId=string
file=string
```

### Responses

**200** – Attachment stored successfully

Content-Type: `application/json`

```json
{
  "ok": true,
  "item": {
    "id": "string",
    "url": "string",
    "fileName": "string",
    "fileSize": 1,
    "content": null
  }
}
```

**400** – Payload validation error

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Attachment violates field constraints

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/attachments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: multipart/form-data" \
  -d "{
  \"entityId\": \"string\",
  \"recordId\": \"string\",
  \"file\": \"string\"
}"
```

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

**Tags:** Attachments

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – File content with appropriate MIME type

Content-Type: `application/json`

**400** – Missing attachment ID

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized - authentication required for private partitions

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden - insufficient permissions

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Attachment or file not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Partition misconfigured

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/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).

**Tags:** Attachments

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| slug | path | any | Optional |

### Responses

**200** – Binary image content (Content-Type: image/jpeg, image/png, etc.)

Content-Type: `application/json`

**400** – Invalid parameters, missing ID, or non-image attachment

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized - authentication required for private partitions

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden - insufficient permissions

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Image not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Partition misconfigured or image rendering failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/attachments/image/:id/:slug" \
  -H "Accept: application/json"
```

## GET `/attachments/library`

List attachments

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

**Tags:** Attachments

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional. Page number for pagination |
| pageSize | query | any | Optional. Number of items per page (max 100) |
| search | query | any | Optional. Search by file name (case-insensitive) |
| partition | query | any | Optional. Filter by partition code |
| tags | query | any | Optional. Filter by tags (comma-separated) |
| sortField | query | any | Optional. Field to sort by |
| sortDir | query | any | Optional. Sort direction |

### Responses

**200** – Attachments list with pagination and metadata

Content-Type: `application/json`

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

**400** – Invalid query parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/attachments/library?page=1&pageSize=25" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/attachments/library/{id}`

Delete attachment

Permanently deletes an attachment file from storage and database. Emits CRUD side effects.

**Tags:** Attachments

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Attachment deleted successfully

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid attachment ID

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Attachment not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/attachments/library/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/attachments/library/{id}`

Get attachment details

Returns complete details of an attachment including metadata, tags, assignments, and custom fields.

**Tags:** Attachments

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Attachment details

Content-Type: `application/json`

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

**400** – Invalid attachment ID

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Attachment not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/attachments/library/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/attachments/library/{id}`

Update attachment metadata

Updates attachment tags, assignments, and custom fields. Emits CRUD side effects for indexing and events.

**Tags:** Attachments

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Attachment updated successfully

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid payload or attachment ID

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Attachment not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to save attributes

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/api/attachments/library/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## DELETE `/attachments/partitions`

Delete partition

Deletes a partition. Default partitions cannot be deleted. Partitions with existing attachments cannot be deleted.

**Tags:** Attachments

**Requires authentication.**

### Responses

**200** – Partition deleted successfully

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid ID or default partition deletion attempt

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Partitions locked in demo mode

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Partition not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Partition in use

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/attachments/partitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/attachments/partitions`

List all attachment partitions

Returns all configured attachment partitions with storage settings, OCR configuration, and access control settings.

**Tags:** Attachments

**Requires authentication.**

### Responses

**200** – List of partitions

Content-Type: `application/json`

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

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/attachments/partitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/attachments/partitions`

Create new partition

Creates a new attachment partition with specified storage and OCR settings. Requires unique partition code.

**Tags:** Attachments

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**201** – Partition created successfully

Content-Type: `application/json`

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

**400** – Invalid payload or partition code

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Partitions locked in demo mode

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Partition code already exists

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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`

Update partition

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

**Tags:** Attachments

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Partition updated successfully

Content-Type: `application/json`

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

**400** – Invalid payload or code change attempt

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Partitions locked in demo mode

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Partition not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/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\"
}"
```

## POST `/attachments/transfer`

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.

**Tags:** Attachments

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Attachments transferred successfully

Content-Type: `application/json`

```json
{
  "ok": true,
  "updated": 1
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Attachments not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Attachment model missing

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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\"
}"
```

## GET `/audit_logs/audit-logs/access`

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.

**Tags:** Audit & Action Logs

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| organizationId | query | any | Optional. Limit results to a specific organization |
| actorUserId | query | any | Optional. Filter by actor user id (tenant administrators only) |
| resourceKind | query | any | Optional. Restrict to a resource kind such as `order` or `product` |
| accessType | query | any | Optional. Access type filter, e.g. `read` or `export` |
| page | query | any | Optional. Page number (default 1) |
| pageSize | query | any | Optional. Page size (default 50) |
| limit | query | any | Optional. Explicit maximum number of records when paginating manually |
| before | query | any | Optional. Return logs created before this ISO-8601 timestamp |
| after | query | any | Optional. Return logs created after this ISO-8601 timestamp |

### Responses

**200** – Access logs returned successfully

Content-Type: `application/json`

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

**400** – Invalid filters supplied

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Caller has no resolved tenant scope and is not a superadmin

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/audit_logs/audit-logs/access" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/audit_logs/audit-logs/actions`

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.

**Tags:** Audit & Action Logs

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| organizationId | query | any | Optional. Limit results to a specific organization |
| actorUserId | query | any | Optional. Filter logs created by specific actor IDs (tenant administrators only). Accepts a single UUID or a comma-separated UUID list. |
| resourceKind | query | any | Optional. Filter by resource kind (e.g., "order", "product") |
| resourceId | query | any | Optional. Filter by resource ID (UUID of the specific record) |
| actionType | query | any | Optional. Filter by action type (`create`, `edit`, `delete`, `assign`). Accepts a single value or a comma-separated list. |
| fieldName | query | any | Optional. Filter to entries where the given field changed. Accepts a single field name or a comma-separated list. |
| includeRelated | query | any | Optional. When `true`, also returns changes to child entities linked via parentResourceKind/parentResourceId |
| includeTotal | query | any | Optional. When `true`, the response includes the filtered total count. |
| undoableOnly | query | any | Optional. When `true`, only undoable actions are returned |
| limit | query | any | Optional. Maximum number of records to return (default 50, max 1000) |
| offset | query | any | Optional. Zero-based record offset for pagination (legacy — prefer page/pageSize) |
| page | query | any | Optional. Page number (default 1) |
| pageSize | query | any | Optional. Page size (default 50, max 200) |
| sortField | query | any | Optional. Sort field: `createdAt`, `user`, `action`, `field`, or `source`. |
| sortDir | query | any | Optional. Sort direction: `asc` or `desc`. |
| before | query | any | Optional. Return actions created before this ISO-8601 timestamp |
| after | query | any | Optional. Return actions created after this ISO-8601 timestamp |

### Responses

**200** – Action logs retrieved successfully

Content-Type: `application/json`

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

**400** – Invalid filter values

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Caller has no resolved tenant scope and is not a superadmin

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/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`

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.

**Tags:** Audit & Action Logs

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| organizationId | query | any | Optional. Limit results to a specific organization |
| actorUserId | query | any | Optional. Filter logs created by specific actor IDs (tenant administrators only). Accepts a single UUID or a comma-separated UUID list. |
| resourceKind | query | any | Optional. Filter by resource kind (e.g., "order", "product") |
| resourceId | query | any | Optional. Filter by resource ID (UUID of the specific record) |
| actionType | query | any | Optional. Filter by action type (`create`, `edit`, `delete`, `assign`). Accepts a single value or a comma-separated list. |
| fieldName | query | any | Optional. Filter to entries where the given field changed. Accepts a single field name or a comma-separated list. |
| includeRelated | query | any | Optional. When `true`, also returns changes to child entities linked via parentResourceKind/parentResourceId |
| undoableOnly | query | any | Optional. When `true`, only undoable actions are returned |
| limit | query | any | Optional. Maximum number of records to export (default 1000, capped at 1000) |
| sortField | query | any | Optional. Sort field: `createdAt`, `user`, `action`, `field`, or `source`. |
| sortDir | query | any | Optional. Sort direction: `asc` or `desc`. |
| before | query | any | Optional. Return actions created before this ISO-8601 timestamp |
| after | query | any | Optional. Return actions created after this ISO-8601 timestamp |

### Responses

**200** – CSV export generated successfully

Content-Type: `application/json`

```json
{
  "file": "csv"
}
```

**400** – Invalid filter values

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Caller has no resolved tenant scope and is not a superadmin

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/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`

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.

**Tags:** Audit & Action Logs

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "logId": "string"
}
```

### Responses

**200** – Redo executed successfully

Content-Type: `application/json`

```json
{
  "ok": true,
  "logId": null,
  "undoToken": null
}
```

**400** – Log not eligible for redo

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Redo blocked by scope checks

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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`

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.

**Tags:** Audit & Action Logs

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "undoToken": "string"
}
```

### Responses

**200** – Undo applied successfully

Content-Type: `application/json`

```json
{
  "ok": true,
  "logId": "string"
}
```

**400** – Invalid or unavailable undo token

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Undo blocked by organization or tenant scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**422** – Undo deliberately blocked by a beforeUndo command interceptor. The interceptor chooses the status (any 4xx/5xx) and may replace the body.

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/audit_logs/audit-logs/actions/undo" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"undoToken\": \"string\"
}"
```

## GET `/auth/admin/nav`

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.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Responses

**200** – Backend chrome payload

Content-Type: `application/json`

```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"
  ],
  "currentOrganization": null
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/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.

**Tags:** Authentication & Accounts

### Responses

**200** – Success response

Content-Type: `application/json`

**307** – Redirect into the app (or to /login on failure)

Content-Type: `text/html`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/auth/autologin" \
  -H "Accept: application/json"
```

## POST `/auth/feature-check`

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.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "features": [
    "string"
  ]
}
```

### Responses

**200** – Evaluation result

Content-Type: `application/json`

```json
{
  "ok": true,
  "granted": [
    "string"
  ],
  "userId": "string"
}
```

**400** – Invalid request — features array missing, too large, or contains invalid entries

Content-Type: `application/json`

```json
{
  "ok": false,
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "ok": false,
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/auth/feature-check" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"features\": [
    \"string\"
  ]
}"
```

## GET `/auth/features`

List declared feature flags

Returns all static features contributed by the enabled modules along with their module source.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Responses

**200** – Aggregated feature catalog

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "string",
      "title": "string",
      "module": "string"
    }
  ],
  "modules": [
    {
      "id": "string",
      "title": "string"
    }
  ]
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/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.

**Tags:** Authentication & Accounts

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| locale | query | any | Required |
| redirect | query | any | Optional |

### Responses

**200** – Success response

Content-Type: `application/json`

**302** – Locale cookie set and request redirected

Content-Type: `application/json`

**400** – Invalid locale

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/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.

**Tags:** Authentication & Accounts

### Request Body

Content-Type: `application/json`

```json
{
  "locale": "en"
}
```

### Responses

**200** – Locale cookie set

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid locale or malformed request body

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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.

**Tags:** Authentication & Accounts

### Request Body

Content-Type: `application/x-www-form-urlencoded`

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

### Responses

**200** – Authentication succeeded

Content-Type: `application/json`

```json
{
  "ok": true,
  "token": "string",
  "redirect": null
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "ok": false,
  "error": "string"
}
```

**401** – Invalid credentials

Content-Type: `application/json`

```json
{
  "ok": false,
  "error": "string"
}
```

**403** – User lacks required role

Content-Type: `application/json`

```json
{
  "ok": false,
  "error": "string"
}
```

**429** – Too many login attempts

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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`

Invalidate session and redirect

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

**Tags:** Authentication & Accounts

**Requires authentication.**

### Responses

**201** – Success response

Content-Type: `application/json`

**302** – Redirect to login after successful logout

Content-Type: `text/html`

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/auth/logout" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/auth/profile`

Get current profile

Returns the email address, display name, and roles for the signed-in user. The display name is null when unset.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Responses

**200** – Profile payload

Content-Type: `application/json`

```json
{
  "email": "user@example.com",
  "name": null,
  "roles": [
    "string"
  ]
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – User not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/auth/profile" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/auth/profile`

Update current profile

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

**Tags:** Authentication & Accounts

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Profile updated

Content-Type: `application/json`

```json
{
  "ok": true,
  "email": "user@example.com"
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/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.

**Tags:** Authentication & Accounts

### Request Body

Content-Type: `application/x-www-form-urlencoded`

```text
email=user%40example.com
```

### Responses

**200** – Reset email dispatched (or ignored for unknown accounts)

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid request origin

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**429** – Too many password reset requests

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Password reset email origin is not configured

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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.

**Tags:** Authentication & Accounts

### Request Body

Content-Type: `application/x-www-form-urlencoded`

```text
token=string&password=string
```

### Responses

**200** – Password reset succeeded

Content-Type: `application/json`

```json
{
  "ok": true,
  "redirect": "string"
}
```

**400** – Invalid token or payload

Content-Type: `application/json`

```json
{
  "ok": false,
  "error": "string"
}
```

**429** – Too many reset confirmation attempts

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/auth/reset/confirm" \
  -H "Accept: application/json" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=string&password=string"
```

## DELETE `/auth/roles`

Delete role

Deletes a role by identifier. Fails when users remain assigned.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Required. Role identifier |

### Responses

**200** – Role deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Role cannot be deleted

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/auth/roles?id=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/auth/roles`

List roles

Returns available roles within the current tenant. Super administrators receive visibility across tenants.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| tenantId | query | any | Optional |

### Responses

**200** – Role collection

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "usersCount": 1,
      "tenantId": null,
      "tenantName": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/auth/roles?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/auth/roles`

Create role

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

**Tags:** Authentication & Accounts

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string"
}
```

### Responses

**201** – Role created

Content-Type: `application/json`

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

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/auth/roles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\"
}"
```

## PUT `/auth/roles`

Update role

Updates mutable fields on an existing role.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Role updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/auth/roles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/auth/roles/acl`

Fetch role ACL

Returns the feature and organization assignments associated with a role within the current tenant.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| roleId | query | any | Required |
| tenantId | query | any | Optional |

### Responses

**200** – Role ACL entry

Content-Type: `application/json`

```json
{
  "isSuperAdmin": true,
  "features": [
    "string"
  ],
  "organizations": null,
  "updatedAt": null
}
```

**400** – Invalid role id

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/auth/roles/acl?roleId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/auth/roles/acl`

Update role ACL

Replaces the feature list, super admin flag, and optional organization assignments for a role.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Role ACL updated

Content-Type: `application/json`

```json
{
  "ok": true,
  "sanitized": true
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Insufficient privileges to modify ACL

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/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.

**Tags:** Authentication & Accounts

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| redirect | query | any | Optional. Absolute or relative URL to redirect after refresh |

### Responses

**200** – Success response

Content-Type: `application/json`

**302** – Redirect to target location when session is valid

Content-Type: `text/html`

### Example

```bash
curl -X GET "https://demo.hoopi.io/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.

**Tags:** Authentication & Accounts

### Request Body

Content-Type: `application/json`

```json
{
  "refreshToken": "string"
}
```

### Responses

**200** – New access token issued

Content-Type: `application/json`

```json
{
  "ok": true,
  "accessToken": "string",
  "expiresIn": 1
}
```

**400** – Missing refresh token

Content-Type: `application/json`

```json
{
  "ok": false,
  "error": "string"
}
```

**401** – Invalid or expired token

Content-Type: `application/json`

```json
{
  "ok": false,
  "error": "string"
}
```

**429** – Too many refresh attempts

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/auth/session/refresh" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"refreshToken\": \"string\"
}"
```

## DELETE `/auth/sidebar/preferences`

Delete a role sidebar variant

Removes the role variant for the current tenant + locale. Idempotent. Requires `auth.sidebar.manage`.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Responses

**200** – Variant deleted (or never existed)

Content-Type: `application/json`

```json
{
  "ok": true,
  "scope": {
    "type": "user"
  }
}
```

**400** – Missing roleId query parameter

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing features

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found in current tenant scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/auth/sidebar/preferences" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/auth/sidebar/preferences`

Get sidebar preferences

Returns sidebar customization for the current user (default) or the specified role (`?roleId=…`, requires `auth.sidebar.manage`).

**Tags:** Authentication & Accounts

**Requires authentication.**

### Responses

**200** – Current sidebar configuration

Content-Type: `application/json`

```json
{
  "locale": "string",
  "settings": {
    "version": 1,
    "groupOrder": [
      "string"
    ],
    "groupLabels": {
      "key": "string"
    },
    "itemLabels": {
      "key": "string"
    },
    "hiddenItems": [
      "string"
    ],
    "itemOrder": {
      "key": [
        "string"
      ]
    }
  },
  "canApplyToRoles": true,
  "roles": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "hasPreference": true
    }
  ],
  "scope": {
    "type": "user"
  },
  "updatedAt": null
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing features for role-scope read

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found in current tenant scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/auth/sidebar/preferences" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/auth/sidebar/preferences`

Update sidebar preferences

Updates sidebar configuration. With `scope.type === "user"` (default) writes the calling user's personal preferences and may optionally apply the same settings to selected roles via `applyToRoles[]`. With `scope.type === "role"` writes the named role variant directly (requires `auth.sidebar.manage`); `applyToRoles[]` and `clearRoleIds[]` are rejected in this mode.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Preferences saved

Content-Type: `application/json`

```json
{
  "locale": "string",
  "settings": {
    "version": 1,
    "groupOrder": [
      "string"
    ],
    "groupLabels": {
      "key": "string"
    },
    "itemLabels": {
      "key": "string"
    },
    "hiddenItems": [
      "string"
    ],
    "itemOrder": {
      "key": [
        "string"
      ]
    }
  },
  "canApplyToRoles": true,
  "roles": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "hasPreference": true
    }
  ],
  "scope": {
    "type": "user"
  },
  "updatedAt": null,
  "appliedRoles": [
    "00000000-0000-4000-8000-000000000000"
  ],
  "clearedRoles": [
    "00000000-0000-4000-8000-000000000000"
  ]
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing features for role-wide updates

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found in current tenant scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/auth/sidebar/preferences" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## GET `/auth/sidebar/variants`

List sidebar variants

Returns the named sidebar variants saved by the current user for the current tenant + locale.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Responses

**200** – Variant list

Content-Type: `application/json`

```json
{
  "locale": "string",
  "variants": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "isActive": true,
      "settings": {
        "version": 1,
        "groupOrder": [
          "string"
        ],
        "groupLabels": {
          "key": "string"
        },
        "itemLabels": {
          "key": "string"
        },
        "hiddenItems": [
          "string"
        ],
        "itemOrder": {
          "key": [
            "string"
          ]
        }
      },
      "createdAt": "string",
      "updatedAt": null
    }
  ]
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/auth/sidebar/variants" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/auth/sidebar/variants`

Create a sidebar variant

Creates a new variant. If `name` is omitted or blank, an auto-name like "My preferences", "My preferences 2", … is assigned.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Variant created

Content-Type: `application/json`

```json
{
  "locale": "string",
  "variant": {
    "id": "00000000-0000-4000-8000-000000000000",
    "name": "string",
    "isActive": true,
    "settings": {
      "version": 1,
      "groupOrder": [
        "string"
      ],
      "groupLabels": {
        "key": "string"
      },
      "itemLabels": {
        "key": "string"
      },
      "hiddenItems": [
        "string"
      ],
      "itemOrder": {
        "key": [
          "string"
        ]
      }
    },
    "createdAt": "string",
    "updatedAt": null
  }
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/auth/sidebar/variants" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## DELETE `/auth/sidebar/variants/{id}`

Delete a sidebar variant

Soft-deletes the variant (sets deleted_at).

**Tags:** Authentication & Accounts

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Variant deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Variant not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/auth/sidebar/variants/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/auth/sidebar/variants/{id}`

Get a sidebar variant

**Tags:** Authentication & Accounts

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Variant

Content-Type: `application/json`

```json
{
  "locale": "string",
  "variant": {
    "id": "00000000-0000-4000-8000-000000000000",
    "name": "string",
    "isActive": true,
    "settings": {
      "version": 1,
      "groupOrder": [
        "string"
      ],
      "groupLabels": {
        "key": "string"
      },
      "itemLabels": {
        "key": "string"
      },
      "hiddenItems": [
        "string"
      ],
      "itemOrder": {
        "key": [
          "string"
        ]
      }
    },
    "createdAt": "string",
    "updatedAt": null
  }
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Variant not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/auth/sidebar/variants/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/auth/sidebar/variants/{id}`

Update a sidebar variant

Updates the variant's name, settings, and/or isActive flag. Setting `isActive: true` deactivates other variants in the same scope (only one active per user/tenant/locale).

**Tags:** Authentication & Accounts

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Variant updated

Content-Type: `application/json`

```json
{
  "locale": "string",
  "variant": {
    "id": "00000000-0000-4000-8000-000000000000",
    "name": "string",
    "isActive": true,
    "settings": {
      "version": 1,
      "groupOrder": [
        "string"
      ],
      "groupLabels": {
        "key": "string"
      },
      "itemLabels": {
        "key": "string"
      },
      "hiddenItems": [
        "string"
      ],
      "itemOrder": {
        "key": [
          "string"
        ]
      }
    },
    "createdAt": "string",
    "updatedAt": null
  }
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Variant not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/auth/sidebar/variants/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## DELETE `/auth/users`

Delete user

Deletes a user by identifier. Rejected when the target is the last active holder of a protected role in the tenant. Undo support is provided via the command bus.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Required. User identifier |

### Responses

**200** – User deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – User cannot be deleted, or is the last active holder of a protected role

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – User not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/auth/users?id=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/auth/users`

List users

Returns users for the effective selected tenant and organization scope. Search matches email, organization name, and role name. Super administrators may scope the response via the topbar context, organization filters, or role filters. Pass scopeToActiveOrganization=1 to restrict results to the caller's active organization (used by recipient/assignee pickers so suggestions stay within the org that owns the resulting record).

**Tags:** Authentication & Accounts

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| name | query | any | Optional |
| organizationId | query | any | Optional |
| scopeToActiveOrganization | query | any | Optional |
| roleIds | query | any | Optional |

### Responses

**200** – User collection

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "email": "user@example.com",
      "name": null,
      "organizationId": null,
      "organizationName": null,
      "tenantId": null,
      "tenantName": null,
      "roles": [
        "string"
      ],
      "isConfirmed": true,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/auth/users?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/auth/users`

Create user

Creates a new confirmed user within the specified organization, optional display name, and optional roles.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "email": "user@example.com",
  "name": null,
  "organizationId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**201** – User created

Content-Type: `application/json`

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

**400** – Invalid payload or duplicate email

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Attempted to assign privileged roles

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/auth/users" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"email\": \"user@example.com\",
  \"name\": null,
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## PUT `/auth/users`

Update user

Updates profile fields including display name, organization assignment, credentials, or role memberships. A destination organization must be within the caller's descendant-expanded organization scope. Retained and newly assigned roles must belong to the destination tenant and be grantable by the caller. Setting isConfirmed=false deactivates the account: the user can no longer sign in and every active session is revoked; isConfirmed=true reactivates it. A tenant cannot drop below a protected role's minimum active holder count, so revoking the role from, deactivating, moving, or deleting the last active administrator is rejected.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "name": null
}
```

### Responses

**200** – User updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid payload, duplicate email, or the update would remove the last active holder of a protected role

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Destination organization is outside caller scope, or a retained or assigned role is not grantable

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – User or destination organization not found in the caller tenant scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/auth/users" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": null
}"
```

## GET `/auth/users/acl`

Fetch user ACL

Returns custom ACL overrides for a user within the current tenant, if any.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| userId | query | any | Required |

### Responses

**200** – User ACL entry

Content-Type: `application/json`

```json
{
  "hasCustomAcl": true,
  "isSuperAdmin": true,
  "features": [
    "string"
  ],
  "organizations": null,
  "updatedAt": null
}
```

**400** – Invalid user id

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/auth/users/acl?userId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/auth/users/acl`

Update user ACL

Updates a per-user ACL override. Omitted super admin, feature, and organization fields preserve their stored values. An organization-scoped non-super-admin override requires at least one feature grant.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "userId": "00000000-0000-4000-8000-000000000000",
  "organizations": null
}
```

### Responses

**200** – User ACL updated

Content-Type: `application/json`

```json
{
  "ok": true,
  "sanitized": true
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Insufficient privileges to modify ACL

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/auth/users/acl" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"userId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizations\": null
}"
```

## GET `/auth/users/consents`

List user consents

Returns all consent records for a given user, with integrity verification status.

**Tags:** Auth

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| userId | query | any | Required |

### Responses

**200** – Consent list returned

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/auth/users/consents?userId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/auth/users/resend-invite`

Resend invitation email

Resends the invitation email to a user who has not yet set up their password. Generates a new 48-hour setup token and invalidates prior tokens.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Invite email sent

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid request origin

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – User not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – User already has a password

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**422** – Validation error

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**429** – Rate limit exceeded

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Invitation email origin is not configured

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/auth/users/resend-invite" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/configs/cache`

Get cache statistics

Returns detailed cache statistics including total entries and breakdown by cache segments. Requires cache service to be available.

**Tags:** Configs

**Requires authentication.**

### Responses

**200** – Cache statistics

Content-Type: `application/json`

```json
{
  "generatedAt": "string",
  "totalKeys": 1,
  "segments": [
    {
      "segment": "string",
      "resource": null,
      "method": null,
      "path": null,
      "keyCount": 1,
      "keys": [
        "string"
      ]
    }
  ]
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to resolve cache stats

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**503** – Cache service unavailable

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/configs/cache" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/configs/cache`

Purge cache

Purges cache entries. Supports two actions: purgeAll (clears entire cache) or purgeSegment (clears specific segment). Returns updated cache statistics after purge.

**Tags:** Configs

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "action": "purgeAll"
}
```

### Responses

**200** – Cache segment cleared successfully

Content-Type: `application/json`

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

**400** – Invalid request - missing segment identifier for purgeSegment action

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to purge cache

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**503** – Cache service unavailable

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/configs/cache" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"action\": \"purgeAll\"
}"
```

## DELETE `/configs/module-telemetry`

Clear module telemetry data

Development-only endpoint that clears in-memory module telemetry and local process telemetry files.

**Tags:** Configs

**Requires authentication.**

### Responses

**200** – Module telemetry cleared

Content-Type: `application/json`

```json
{
  "cleared": true
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden outside development mode

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/configs/module-telemetry" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/configs/module-telemetry`

Get module resource usage telemetry

Returns in-process module resource attribution for API routes, event subscribers, and queue workers.

**Tags:** Configs

**Requires authentication.**

### Responses

**200** – Module resource usage report

Content-Type: `application/json`

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

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/configs/module-telemetry" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/configs/system-status`

Get system health status

Returns comprehensive system health information including environment details, version, resource usage, and service connectivity status.

**Tags:** Configs

**Requires authentication.**

### Responses

**200** – System status snapshot

Content-Type: `application/json`

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

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to load system status

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/configs/system-status" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/configs/system-status`

Clear system cache

Purges the entire cache for the current tenant. Useful for troubleshooting or forcing fresh data loading.

**Tags:** Configs

**Requires authentication.**

### Responses

**200** – Cache cleared successfully

Content-Type: `application/json`

```json
{
  "cleared": true
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to purge cache

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**503** – Cache service unavailable

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/configs/system-status" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/configs/upgrade-actions`

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.

**Tags:** Configs

**Requires authentication.**

### Responses

**200** – List of pending upgrade actions

Content-Type: `application/json`

```json
{
  "version": "string",
  "actions": [
    {
      "id": "string",
      "version": "string",
      "message": "string",
      "ctaLabel": "string",
      "successMessage": "string",
      "loadingLabel": "string"
    }
  ]
}
```

**400** – Missing organization or tenant context

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to load upgrade actions

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/configs/upgrade-actions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/configs/upgrade-actions`

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.

**Tags:** Configs

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "actionId": "string"
}
```

### Responses

**200** – Upgrade action executed successfully

Content-Type: `application/json`

```json
{
  "status": "string",
  "message": "string",
  "version": "string"
}
```

**400** – Invalid request body or missing context

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Upgrade actions are disabled

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to execute upgrade action

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/configs/upgrade-actions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"actionId\": \"string\"
}"
```

## DELETE `/customers/activities`

Delete activity

DEPRECATED (sunset 2026-06-30): Deletes an activity. Use DELETE /api/customers/interactions instead.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Activity deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/activities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/activities`

List activitys

Returns a paginated collection of activitys scoped to the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| entityId | query | any | Optional |
| dealId | query | any | Optional |
| activityType | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated activitys

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "activityType": "string",
      "subject": null,
      "body": null,
      "occurredAt": null,
      "createdAt": "string",
      "appearanceIcon": null,
      "appearanceColor": null,
      "entityId": null,
      "authorUserId": null,
      "authorName": null,
      "authorEmail": null,
      "dealId": null,
      "dealTitle": null,
      "customValues": null,
      "activityTypeLabel": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/activities?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/activities`

Create activity

DEPRECATED (sunset 2026-06-30): Creates a timeline activity. Use POST /api/customers/interactions instead.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "00000000-0000-4000-8000-000000000000",
  "activityType": "string",
  "phoneNumber": null,
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**201** – Activity created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/activities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"00000000-0000-4000-8000-000000000000\",
  \"activityType\": \"string\",
  \"phoneNumber\": null,
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## PUT `/customers/activities`

Update activity

DEPRECATED (sunset 2026-06-30): Updates an activity. Use PUT /api/customers/interactions instead.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "phoneNumber": null,
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**200** – Activity updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/activities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"phoneNumber\": null,
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## DELETE `/customers/addresses`

Delete address

Deletes an address by id. The identifier may be included in the body or query.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Address deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/addresses" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/addresses`

List addresss

Returns a paginated collection of addresss scoped to the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| entityId | query | any | Optional |
| id | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated addresss

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "entity_id": "00000000-0000-4000-8000-000000000000",
      "name": null,
      "purpose": null,
      "company_name": null,
      "address_line1": null,
      "address_line2": null,
      "building_number": null,
      "flat_number": null,
      "city": null,
      "region": null,
      "postal_code": null,
      "country": null,
      "latitude": null,
      "longitude": null,
      "is_primary": null,
      "organization_id": null,
      "tenant_id": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/addresses?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/addresses`

Create address

Creates a customer address record and associates it with the referenced entity.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000",
  "addressLine1": "string",
  "latitude": null,
  "longitude": null
}
```

### Responses

**201** – Address created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/addresses" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\",
  \"addressLine1\": \"string\",
  \"latitude\": null,
  \"longitude\": null
}"
```

## PUT `/customers/addresses`

Update address

Updates fields on an existing customer address.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "latitude": null,
  "longitude": null
}
```

### Responses

**200** – Address updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/addresses" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"latitude\": null,
  \"longitude\": null
}"
```

## GET `/customers/assignable-staff`

DEPRECATED: use GET /api/staff/team-members/assignable instead.

Deprecated. Returns 308 Permanent Redirect to /api/staff/team-members/assignable preserving the query string. Will be removed no earlier than the next major release.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |

### Responses

**200** – Assignable staff members (only reachable by following the redirect).

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamMemberId": "00000000-0000-4000-8000-000000000000",
      "userId": "00000000-0000-4000-8000-000000000000",
      "displayName": "string",
      "email": null,
      "teamName": null,
      "user": null,
      "team": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

**308** – Permanent redirect to /api/staff/team-members/assignable.

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/assignable-staff?page=1&pageSize=24" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/customers/comments`

Delete comment

Deletes a comment identified by `id` supplied via body or query string.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Comment deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/comments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/comments`

List comments

Returns a paginated collection of comments scoped to the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| entityId | query | any | Optional |
| dealId | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated comments

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "entity_id": null,
      "deal_id": null,
      "body": null,
      "author_user_id": null,
      "appearance_icon": null,
      "appearance_color": null,
      "organization_id": null,
      "tenant_id": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/comments?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/comments`

Create comment

Adds a comment to a customer timeline.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000",
  "body": "string",
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**201** – Comment created

Content-Type: `application/json`

```json
{
  "id": null,
  "authorUserId": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/comments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\",
  \"body\": \"string\",
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## PUT `/customers/comments`

Update comment

Updates an existing timeline comment.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**200** – Comment updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/comments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## DELETE `/customers/companies`

Delete company

Deletes a company by id. The identifier can be provided via body or query.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Company deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**422** – Company has dependent records (people, deals, or direct staff); unlink or reassign before delete.

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "COMPANY_HAS_DEPENDENTS"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/companies" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/companies`

List companies

Returns a paginated collection of companies scoped to the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| email | query | any | Optional |
| emailStartsWith | query | any | Optional |
| emailContains | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| status | query | any | Optional |
| lifecycleStage | query | any | Optional |
| source | query | any | Optional |
| hasEmail | query | any | Optional |
| hasPhone | query | any | Optional |
| hasNextInteraction | query | any | Optional |
| createdFrom | query | any | Optional |
| createdTo | query | any | Optional |
| id | query | any | Optional |
| tagIds | query | any | Optional |
| tagIdsEmpty | query | any | Optional |
| excludeIds | query | any | Optional |
| excludeLinkedPersonId | query | any | Optional |
| excludeLinkedCompanyId | query | any | Optional |
| excludeLinkedDealId | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated companies

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "description": null,
      "owner_user_id": null,
      "primary_email": null,
      "primary_phone": null,
      "status": null,
      "lifecycle_stage": null,
      "source": null,
      "next_interaction_at": null,
      "next_interaction_name": null,
      "next_interaction_ref_id": null,
      "next_interaction_icon": null,
      "next_interaction_color": null,
      "organization_id": null,
      "tenant_id": null,
      "created_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/companies?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/companies`

Create company

Creates a company record and associated profile data.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "displayName": "string",
  "description": null,
  "primaryEmail": null,
  "primaryPhone": null,
  "nextInteraction": null,
  "legalName": null,
  "brandName": null,
  "domain": null,
  "websiteUrl": null,
  "sizeBucket": null,
  "annualRevenue": null
}
```

### Responses

**201** – Company created

Content-Type: `application/json`

```json
{
  "id": null,
  "companyId": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/companies" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"displayName\": \"string\",
  \"description\": null,
  \"primaryEmail\": null,
  \"primaryPhone\": null,
  \"nextInteraction\": null,
  \"legalName\": null,
  \"brandName\": null,
  \"domain\": null,
  \"websiteUrl\": null,
  \"sizeBucket\": null,
  \"annualRevenue\": null
}"
```

## PUT `/customers/companies`

Update company

Updates company profile fields, tags, or custom attributes.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "description": null,
  "primaryEmail": null,
  "primaryPhone": null,
  "nextInteraction": null,
  "legalName": null,
  "brandName": null,
  "domain": null,
  "websiteUrl": null,
  "sizeBucket": null,
  "annualRevenue": null
}
```

### Responses

**200** – Company updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/companies" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"description\": null,
  \"primaryEmail\": null,
  \"primaryPhone\": null,
  \"nextInteraction\": null,
  \"legalName\": null,
  \"brandName\": null,
  \"domain\": null,
  \"websiteUrl\": null,
  \"sizeBucket\": null,
  \"annualRevenue\": null
}"
```

## GET `/customers/companies/{id}`

Fetch company with related data

Returns a company customer record with optional related resources such as addresses, comments, activities, interactions, deals, todos, and linked people.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| include | query | any | Optional. Comma-separated list of relations to include (addresses, comments, activities, interactions, deals, todos, people). |

### Responses

**200** – Company detail payload

Content-Type: `application/json`

```json
{
  "interactionMode": "canonical",
  "company": {
    "id": "00000000-0000-4000-8000-000000000000",
    "displayName": null,
    "description": null,
    "ownerUserId": null,
    "primaryEmail": null,
    "primaryPhone": null,
    "status": null,
    "lifecycleStage": null,
    "source": null,
    "nextInteractionAt": null,
    "nextInteractionName": null,
    "nextInteractionRefId": null,
    "nextInteractionIcon": null,
    "nextInteractionColor": null,
    "organizationId": null,
    "tenantId": null,
    "temperature": null,
    "renewalQuarter": null,
    "createdAt": "string",
    "updatedAt": "string"
  },
  "profile": null,
  "customFields": {},
  "tags": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "label": "string",
      "color": null
    }
  ],
  "addresses": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": null,
      "purpose": null,
      "addressLine1": null,
      "addressLine2": null,
      "buildingNumber": null,
      "flatNumber": null,
      "city": null,
      "region": null,
      "postalCode": null,
      "country": null,
      "latitude": null,
      "longitude": null,
      "isPrimary": null,
      "createdAt": "string"
    }
  ],
  "comments": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "body": null,
      "authorUserId": null,
      "authorName": null,
      "authorEmail": null,
      "dealId": null,
      "createdAt": "string",
      "appearanceIcon": null,
      "appearanceColor": null
    }
  ],
  "activities": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "activityType": "string",
      "subject": null,
      "body": null,
      "occurredAt": null,
      "dealId": null,
      "authorUserId": null,
      "authorName": null,
      "authorEmail": null,
      "createdAt": "string",
      "appearanceIcon": null,
      "appearanceColor": null
    }
  ],
  "interactions": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "entityId": null,
      "interactionType": "string",
      "title": null,
      "body": null,
      "status": "string",
      "scheduledAt": null,
      "occurredAt": null,
      "priority": null,
      "authorUserId": null,
      "ownerUserId": null,
      "dealId": null,
      "organizationId": null,
      "tenantId": null,
      "authorName": null,
      "authorEmail": null,
      "dealTitle": null,
      "customValues": null,
      "appearanceIcon": null,
      "appearanceColor": null,
      "source": null,
      "createdAt": "string",
      "updatedAt": "string"
    }
  ],
  "deals": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "title": null,
      "status": null,
      "pipelineStage": null,
      "valueAmount": null,
      "valueCurrency": null,
      "probability": null,
      "expectedCloseAt": null,
      "ownerUserId": null,
      "source": null,
      "createdAt": "string",
      "updatedAt": "string"
    }
  ],
  "todos": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "todoId": "00000000-0000-4000-8000-000000000000",
      "todoSource": "string",
      "createdAt": "string",
      "createdByUserId": null,
      "title": null,
      "isDone": null,
      "priority": null,
      "severity": null,
      "description": null,
      "dueAt": null,
      "todoOrganizationId": null,
      "customValues": null
    }
  ],
  "people": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "displayName": null,
      "primaryEmail": null,
      "primaryPhone": null,
      "status": null,
      "lifecycleStage": null,
      "jobTitle": null,
      "department": null,
      "createdAt": "string",
      "organizationId": null,
      "source": null,
      "temperature": null,
      "linkedAt": null
    }
  ],
  "viewer": {
    "userId": null,
    "name": null,
    "email": null
  }
}
```

**400** – Invalid identifier

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden for tenant/organization scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Company not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/companies/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/companies/{id}/people`

List linked people for a company

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| sort | query | any | Optional |

### Responses

**200** – Paginated linked people

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "displayName": "string",
      "primaryEmail": null,
      "primaryPhone": null,
      "status": null,
      "lifecycleStage": null,
      "jobTitle": null,
      "department": null,
      "createdAt": "string",
      "organizationId": null,
      "temperature": null,
      "source": null,
      "linkedAt": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/companies/:id/people?page=1&pageSize=20&sort=name-asc" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/customers/companies/{id}/roles`

Remove a company role assignment

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| roleId | query | any | Required |

### Responses

**200** – Role deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/companies/00000000-0000-4000-8000-000000000000/roles?roleId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/companies/{id}/roles`

List roles for a company

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Role assignments

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "entityType": "company",
      "entityId": "00000000-0000-4000-8000-000000000000",
      "userId": "00000000-0000-4000-8000-000000000000",
      "userName": null,
      "userEmail": null,
      "userPhone": null,
      "roleType": "string",
      "createdAt": "string",
      "updatedAt": "string"
    }
  ]
}
```

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/companies/00000000-0000-4000-8000-000000000000/roles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/companies/{id}/roles`

Assign a role to a company

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "roleType": "string",
  "userId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**201** – Role created

Content-Type: `application/json`

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

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Role already assigned

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/companies/00000000-0000-4000-8000-000000000000/roles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"roleType\": \"string\",
  \"userId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## PUT `/customers/companies/{id}/roles`

Update a company role assignment

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| roleId | query | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "userId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**200** – Role updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/companies/00000000-0000-4000-8000-000000000000/roles?roleId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"userId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/dashboard/widgets/customer-todos`

Fetch recent customer tasks

Returns the most recent customer tasks for display on dashboards, including legacy compatibility rows when needed.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| limit | query | any | Optional |
| tenantId | query | any | Optional |
| organizationId | query | any | Optional |

### Responses

**200** – Widget payload

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "todoId": "00000000-0000-4000-8000-000000000000",
      "todoSource": "string",
      "todoTitle": null,
      "createdAt": "string",
      "organizationId": null,
      "entity": {
        "id": null,
        "displayName": null,
        "kind": null,
        "ownerUserId": null
      }
    }
  ]
}
```

**400** – Invalid query parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Requested scope is not accessible

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Widget failed to load

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/dashboard/widgets/customer-todos?limit=5" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/dashboard/widgets/new-customers`

Fetch recently created customers

Returns the latest customers created within the scoped tenant/organization for dashboard display.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| limit | query | any | Optional |
| tenantId | query | any | Optional |
| organizationId | query | any | Optional |
| kind | query | any | Optional |

### Responses

**200** – Widget payload

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "displayName": null,
      "kind": null,
      "organizationId": null,
      "createdAt": "string",
      "ownerUserId": null
    }
  ]
}
```

**400** – Invalid query parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Requested scope is not accessible

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Widget failed to load

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/dashboard/widgets/new-customers?limit=5" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/dashboard/widgets/new-deals`

Fetch recently created deals

Returns the latest deals created within the scoped tenant/organization for dashboard display.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| limit | query | any | Optional |
| tenantId | query | any | Optional |
| organizationId | query | any | Optional |

### Responses

**200** – Widget payload

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "title": null,
      "status": null,
      "organizationId": null,
      "createdAt": "string",
      "ownerUserId": null,
      "valueAmount": null,
      "valueCurrency": null
    }
  ]
}
```

**400** – Invalid query parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Requested scope is not accessible

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Widget failed to load

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/dashboard/widgets/new-deals?limit=5" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/dashboard/widgets/next-interactions`

Fetch upcoming customer interactions

Lists upcoming (or optionally past) customer interaction reminders ordered by interaction date.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| limit | query | any | Optional |
| tenantId | query | any | Optional |
| organizationId | query | any | Optional |
| includePast | query | any | Optional |

### Responses

**200** – Widget payload

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "displayName": null,
      "kind": null,
      "organizationId": null,
      "nextInteractionAt": null,
      "nextInteractionName": null,
      "nextInteractionIcon": null,
      "nextInteractionColor": null,
      "ownerUserId": null
    }
  ],
  "now": "string"
}
```

**400** – Invalid query parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Requested scope is not accessible

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Widget failed to load

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/dashboard/widgets/next-interactions?limit=5" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/customers/deals`

Delete deal

Deletes a deal by `id`. The identifier may be provided in the body or query parameters.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Deal deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/deals" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/deals`

List deals

Returns a paginated collection of deals scoped to the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| id | query | any | Optional |
| search | query | any | Optional |
| status | query | any | Optional |
| pipelineStage | query | any | Optional |
| pipelineId | query | any | Optional |
| pipelineStageId | query | any | Optional |
| ownerUserId | query | any | Optional |
| expectedCloseAtFrom | query | any | Optional |
| expectedCloseAtTo | query | any | Optional |
| isStuck | query | any | Optional |
| isOverdue | query | any | Optional |
| needsAttention | query | any | Optional |
| valueCurrency | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| personEntityId | query | any | Optional. Deprecated; use personId |
| companyEntityId | query | any | Optional. Deprecated; use companyId |
| personId | query | any | Optional |
| companyId | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated deals

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "title": null,
      "description": null,
      "status": null,
      "pipeline_stage": null,
      "pipeline_id": null,
      "pipeline_stage_id": null,
      "value_amount": null,
      "value_currency": null,
      "probability": null,
      "expected_close_at": null,
      "owner_user_id": null,
      "source": null,
      "organization_id": null,
      "tenant_id": null,
      "created_at": null,
      "updated_at": null,
      "organizationId": null,
      "tenantId": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/deals?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/deals`

Create deal

Creates a sales deal, optionally associating people and companies.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "title": "string",
  "ownerUserId": null,
  "primaryPersonEntityId": null
}
```

### Responses

**201** – Deal created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/deals" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"title\": \"string\",
  \"ownerUserId\": null,
  \"primaryPersonEntityId\": null
}"
```

## PUT `/customers/deals`

Update deal

Updates pipeline position, metadata, or associations for an existing deal.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "ownerUserId": null,
  "primaryPersonEntityId": null
}
```

### Responses

**200** – Deal updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/deals" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"ownerUserId\": null,
  \"primaryPersonEntityId\": null
}"
```

## GET `/customers/deals/{id}`

Fetch deal with associations and pipeline context

Returns a deal with linked people, companies, closure fields, optional pipeline history, custom fields, and viewer context.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| include | query | any | Optional |

### Responses

**200** – Deal detail payload

Content-Type: `application/json`

```json
{
  "deal": {
    "id": "00000000-0000-4000-8000-000000000000",
    "title": null,
    "description": null,
    "status": null,
    "pipelineStage": null,
    "pipelineId": null,
    "pipelineStageId": null,
    "valueAmount": null,
    "valueCurrency": null,
    "probability": null,
    "expectedCloseAt": null,
    "ownerUserId": null,
    "source": null,
    "closureOutcome": null,
    "lossReasonId": null,
    "lossNotes": null,
    "organizationId": null,
    "tenantId": null,
    "createdAt": "string",
    "updatedAt": "string"
  },
  "people": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "label": "string",
      "subtitle": null,
      "kind": "person"
    }
  ],
  "companies": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "label": "string",
      "subtitle": null,
      "kind": "company"
    }
  ],
  "customFields": {},
  "viewer": {
    "userId": null,
    "name": null,
    "email": null
  },
  "pipelineStages": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "label": "string",
      "order": 1,
      "color": null,
      "icon": null
    }
  ],
  "stageTransitions": [
    {
      "stageId": "00000000-0000-4000-8000-000000000000",
      "stageLabel": "string",
      "stageOrder": 1,
      "transitionedAt": "string"
    }
  ],
  "owner": null
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden for tenant/organization scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Deal not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/deals/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/deals/{id}/companies`

List linked companies for a deal

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| sort | query | any | Optional |

### Responses

**200** – Paginated linked companies

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "label": "string",
      "subtitle": null,
      "kind": "company",
      "linkedAt": "string"
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/deals/:id/companies?page=1&pageSize=20&sort=label-asc" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/deals/{id}/people`

List linked people for a deal

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| sort | query | any | Optional |

### Responses

**200** – Paginated linked people

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "label": "string",
      "subtitle": null,
      "kind": "person",
      "linkedAt": "string",
      "isPrimary": true
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/deals/:id/people?page=1&pageSize=20&sort=label-asc" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/deals/{id}/stats`

Fetch analytics for a closed deal

Returns week-to-date closure counts, sales cycle length, quarter ranking, and loss reason context for a closed deal.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Deal closure stats payload

Content-Type: `application/json`

```json
{
  "dealValue": null,
  "dealCurrency": null,
  "closureOutcome": "won",
  "closedAt": "string",
  "pipelineName": null,
  "dealsClosedThisPeriod": 1,
  "salesCycleDays": null,
  "dealRankInQuarter": null,
  "lossReason": null
}
```

**400** – Deal is not closed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden for tenant/organization scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Deal not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/deals/:id/stats" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/deals/aggregate`

Per-stage counts and currency totals for kanban lane headers

Returns per-stage counts and totals for deals, with values converted to the tenant base currency where rates are available. Used to power kanban lane headers without loading every deal.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| pipelineId | query | any | Optional |
| search | query | any | Optional |
| status | query | any | Optional |
| ownerUserId | query | any | Optional |
| personId | query | any | Optional |
| companyId | query | any | Optional |
| isStuck | query | any | Optional |
| isOverdue | query | any | Optional |
| expectedCloseAtFrom | query | any | Optional |
| expectedCloseAtTo | query | any | Optional |

### Responses

**200** – Per-stage aggregate payload

Content-Type: `application/json`

```json
{
  "baseCurrencyCode": null,
  "perStage": [
    {
      "stageId": "string",
      "count": 1,
      "openCount": 1,
      "totalInBaseCurrency": 1,
      "byCurrency": [
        {
          "currency": "string",
          "total": 1,
          "count": 1
        }
      ],
      "convertedAll": true,
      "missingRateCurrencies": [
        "string"
      ]
    }
  ]
}
```

**400** – Invalid query parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/deals/aggregate" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/deals/bulk-update-owner`

Bulk reassign deal owner

Queues a background job that reassigns the listed deals to a new owner (or clears the owner when null).

**Tags:** Customer Relationship Management

**Requires authentication.**

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/deals/bulk-update-owner" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/deals/bulk-update-stage`

Bulk update deal pipeline stage

Queues a background job that moves the listed deals to the same pipeline stage. Returns a progress job id to poll for completion.

**Tags:** Customer Relationship Management

**Requires authentication.**

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/deals/bulk-update-stage" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/deals/map`

Paginated deals that have a resolvable map location

Returns a page of deals that have a coordinate-bearing linked company/person address, each enriched with one resolved location (company primary first, then earliest created; person addresses as fallback). Deals with no coordinate-bearing address are excluded entirely, so every item carries a non-null location in normal operation; the schema keeps location nullable only for the rare case where the address is deleted between the located-deal resolution and the page fetch.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| id | query | any | Optional |
| search | query | any | Optional |
| status | query | any | Optional |
| pipelineStage | query | any | Optional |
| pipelineId | query | any | Optional |
| pipelineStageId | query | any | Optional |
| ownerUserId | query | any | Optional |
| expectedCloseAtFrom | query | any | Optional |
| expectedCloseAtTo | query | any | Optional |
| isStuck | query | any | Optional |
| isOverdue | query | any | Optional |
| needsAttention | query | any | Optional |
| valueCurrency | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| personEntityId | query | any | Optional. Deprecated; use personId |
| companyEntityId | query | any | Optional. Deprecated; use companyId |
| personId | query | any | Optional |
| companyId | query | any | Optional |

### Responses

**200** – Paged located deals

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "title": null,
      "status": null,
      "pipelineId": null,
      "pipelineStageId": null,
      "pipelineStage": null,
      "valueAmount": null,
      "valueCurrency": null,
      "probability": null,
      "expectedCloseAt": null,
      "ownerUserId": null,
      "updatedAt": null,
      "companies": [
        {
          "id": "00000000-0000-4000-8000-000000000000",
          "label": null
        }
      ],
      "people": [
        {
          "id": "00000000-0000-4000-8000-000000000000",
          "label": null
        }
      ],
      "location": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

**400** – Invalid query parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing required features

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/deals/map?page=1&pageSize=100" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/deals/summary`

Pipeline KPI metrics with period-over-period deltas for the deals list

Returns the four list-level KPI cards (pipeline value, active deals, won this quarter, win rate) with quarter-over-quarter deltas, per-stage open-pipeline breakdown, top owners, and a 6-month win-rate series. Values are converted to the tenant base currency where rates are available; partial conversions are disclosed via convertedAll/missingRateCurrencies.

**Tags:** Customers

**Requires authentication.**

### Responses

**200** – Deals KPI summary payload

Content-Type: `application/json`

```json
{
  "baseCurrencyCode": null,
  "convertedAll": true,
  "missingRateCurrencies": [
    "string"
  ],
  "pipelineValue": {
    "value": 1,
    "delta": {
      "value": 1,
      "direction": "up"
    },
    "stages": [
      {
        "stage": null,
        "count": 1,
        "value": 1
      }
    ]
  },
  "activeDeals": {
    "value": 1,
    "delta": {
      "value": 1,
      "direction": "up"
    },
    "ownersCount": 1,
    "needAttention": 1,
    "owners": [
      {
        "id": "string",
        "count": 1
      }
    ],
    "ownersOverflow": 1
  },
  "wonThisQuarter": {
    "value": 1,
    "delta": {
      "value": 1,
      "direction": "up"
    },
    "dealsClosed": 1,
    "avgDeal": 1
  },
  "winRate": {
    "value": 1,
    "deltaPp": 1,
    "direction": "up",
    "previousValue": 1,
    "series": [
      {
        "period": "string",
        "rate": 1
      }
    ]
  }
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/deals/summary" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/dictionaries/{kind}`

List dictionary entries

Returns dictionary entries for the requested kind within the currently selected organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| kind | path | any | Required |

### Responses

**200** – Dictionary entries

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "value": "string",
      "label": null,
      "color": null,
      "icon": null,
      "organizationId": null
    }
  ]
}
```

**400** – Failed to resolve dictionary context

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/dictionaries/:kind" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/dictionaries/{kind}`

Create or override dictionary entry

Creates a dictionary entry (or updates the existing entry for the same value) within the current organization scope.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| kind | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "value": "string"
}
```

### Responses

**200** – Dictionary entry updated

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "value": "string",
  "label": null,
  "color": null,
  "icon": null,
  "organizationId": null
}
```

**201** – Dictionary entry created

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "value": "string",
  "label": null,
  "color": null,
  "icon": null,
  "organizationId": null
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Duplicate value conflict

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/dictionaries/:kind" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"value\": \"string\"
}"
```

## DELETE `/customers/dictionaries/{kind}/{id}`

Delete dictionary entry

Removes a customer dictionary entry by identifier.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| kind | path | any | Required |
| id | path | any | Required |

### Responses

**200** – Entry deleted

Content-Type: `application/json`

```json
{
  "success": true
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Entry not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Entry is in use and cannot be deleted

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/dictionaries/:kind/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/customers/dictionaries/{kind}/{id}`

Update dictionary entry

Updates value, label, color, or icon for an existing customer dictionary entry.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| kind | path | any | Required |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Updated dictionary entry

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "value": "string",
  "label": null,
  "color": null,
  "icon": null,
  "organizationId": null
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Entry not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Duplicate value conflict

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/api/customers/dictionaries/:kind/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## GET `/customers/dictionaries/currency`

Resolve currency dictionary

Returns the active currency dictionary for the current organization scope, falling back to shared entries when required.

**Tags:** Customers

**Requires authentication.**

### Responses

**200** – Currency dictionary entries

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "entries": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "value": "string",
      "label": null
    }
  ]
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Currency dictionary missing

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Unexpected error

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/dictionaries/currency" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/dictionaries/kind-settings`

List kind settings

Returns selection mode and visibility settings for each dictionary kind.

**Tags:** Customers

**Requires authentication.**

### Responses

**200** – Kind settings

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "kind": "string",
      "selectionMode": "single",
      "visibleInTags": true,
      "sortOrder": 1
    }
  ]
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/dictionaries/kind-settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/customers/dictionaries/kind-settings`

Update kind setting

Creates or updates settings for a specific dictionary kind.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "kind": "string"
}
```

### Responses

**200** – Setting updated

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "kind": "string",
  "selectionMode": "single",
  "visibleInTags": true,
  "sortOrder": 1
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/api/customers/dictionaries/kind-settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"kind\": \"string\"
}"
```

## DELETE `/customers/interactions`

Delete interaction

Soft-deletes an interaction identified by `id`. Accepts id via body or query string.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Interaction deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/interactions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/interactions`

List interactions

Returns a paginated collection of interactions scoped to the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| limit | query | any | Optional |
| cursor | query | any | Optional |
| entityId | query | any | Optional |
| dealId | query | any | Optional |
| status | query | any | Optional |
| interactionType | query | any | Optional |
| type | query | any | Optional |
| excludeInteractionType | query | any | Optional |
| search | query | any | Optional |
| from | query | any | Optional |
| to | query | any | Optional |
| recurrenceMasters | query | any | Optional |
| pinned | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated interactions

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "entityId": null,
      "dealId": null,
      "interactionType": "string",
      "title": null,
      "body": null,
      "status": "string",
      "scheduledAt": null,
      "occurredAt": null,
      "priority": null,
      "authorUserId": null,
      "ownerUserId": null,
      "appearanceIcon": null,
      "appearanceColor": null,
      "source": null,
      "duration": null,
      "durationMinutes": null,
      "location": null,
      "allDay": null,
      "recurrenceRule": null,
      "recurrenceEnd": null,
      "participants": null,
      "reminderMinutes": null,
      "visibility": null,
      "linkedEntities": null,
      "guestPermissions": null,
      "organizationId": null,
      "tenantId": null,
      "createdAt": null,
      "updatedAt": null,
      "authorName": null,
      "authorEmail": null,
      "dealTitle": null,
      "customValues": null
    }
  ]
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/interactions?limit=25" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/interactions`

Create interaction

Creates a new interaction linked to a customer entity or deal.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

No example available for this content type.

### Responses

**201** – Interaction created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/interactions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/customers/interactions`

Update interaction

Updates fields for an existing interaction.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

No example available for this content type.

### Responses

**200** – Interaction updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/interactions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/customers/interactions/{id}/visibility`

Flip an email interaction visibility (private ↔ shared)

**Tags:** Customers, Email

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Updated

Content-Type: `application/json`

**400** – Invalid id

Content-Type: `application/json`

**401** – Unauthorized

Content-Type: `application/json`

**404** – Email not found or not visible to caller

Content-Type: `application/json`

**422** – Invalid body

Content-Type: `application/json`

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/api/customers/interactions/:id/visibility" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/interactions/cancel`

Cancel an interaction

Marks an interaction as canceled.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Interaction canceled

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Interaction not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/interactions/cancel" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/customers/interactions/complete`

Complete an interaction

Marks an interaction as done and sets occurredAt to current time (or a provided timestamp).

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Interaction completed

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Interaction not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/interactions/complete" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/interactions/conflicts`

Detect scheduling conflicts

Checks for overlapping planned interactions within the requested time window.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| date | query | any | Required |
| startTime | query | any | Required |
| duration | query | any | Required |
| excludeId | query | any | Optional |
| userId | query | any | Optional |
| timezoneOffsetMinutes | query | any | Optional |

### Responses

**200** – Conflict detection result

Content-Type: `application/json`

```json
{
  "ok": true,
  "result": {
    "hasConflicts": true,
    "conflicts": [
      {
        "id": "string",
        "title": null,
        "startTime": "string",
        "endTime": "string",
        "type": "string"
      }
    ]
  }
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/interactions/conflicts?date=string&startTime=string&duration=1" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/interactions/counts`

Get interaction counts by type

Returns per-type interaction counts scoped to an entity.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| entityId | query | any | Required |
| status | query | any | Optional |

### Responses

**200** – Counts by interaction type

Content-Type: `application/json`

```json
{
  "ok": true,
  "result": {
    "call": 1,
    "email": 1,
    "meeting": 1,
    "note": 1,
    "task": 1,
    "total": 1
  }
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/interactions/counts?entityId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/interactions/tasks`

List customertasks

Returns a paginated collection of customertasks scoped to the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| all | query | any | Optional |
| entityId | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated customertasks

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "string",
      "todoId": "string",
      "todoSource": "string",
      "todoTitle": null,
      "todoIsDone": null,
      "todoPriority": null,
      "todoSeverity": null,
      "todoDescription": null,
      "todoDueAt": null,
      "todoCustomValues": null,
      "todoOrganizationId": null,
      "organizationId": "string",
      "tenantId": "string",
      "createdAt": "string",
      "externalHref": null,
      "customer": {
        "id": null,
        "displayName": null,
        "kind": null
      }
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/interactions/tasks?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/labels`

List labels

Returns labels for the current user within the selected organization. Optionally includes assignment status for a specific entity.

**Tags:** Customers

**Requires authentication.**

### Responses

**200** – Labels list

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "slug": "string",
      "label": "string"
    }
  ],
  "assignedIds": [
    "00000000-0000-4000-8000-000000000000"
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/labels" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/labels`

Create label

Creates a new label scoped to the current user and selected organization.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "label": "string"
}
```

### Responses

**201** – Label created

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "slug": "string",
  "label": "string"
}
```

**409** – Duplicate slug

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/labels" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"label\": \"string\"
}"
```

## POST `/customers/labels/assign`

Assign label

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "labelId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**200** – Already assigned

Content-Type: `application/json`

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

**201** – Assigned

Content-Type: `application/json`

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

**404** – Label or entity not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/labels/assign" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"labelId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/customers/labels/unassign`

Unassign label

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "labelId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**200** – Unassigned

Content-Type: `application/json`

```json
{
  "id": null
}
```

**404** – Label or entity not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/labels/unassign" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"labelId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## DELETE `/customers/people`

Delete person

Deletes a person by id. Request body or query may provide the identifier.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Person deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**422** – Person has dependent records (e.g. linked deals); unlink or reassign before delete.

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "PERSON_HAS_DEPENDENTS"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/people" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/people`

List people

Returns a paginated collection of people scoped to the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| email | query | any | Optional |
| emailStartsWith | query | any | Optional |
| emailContains | query | any | Optional |
| status | query | any | Optional |
| lifecycleStage | query | any | Optional |
| source | query | any | Optional |
| hasEmail | query | any | Optional |
| hasPhone | query | any | Optional |
| hasNextInteraction | query | any | Optional |
| createdFrom | query | any | Optional |
| createdTo | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| id | query | any | Optional |
| tagIds | query | any | Optional |
| tagIdsEmpty | query | any | Optional |
| excludeIds | query | any | Optional |
| excludeLinkedCompanyId | query | any | Optional |
| excludeLinkedDealId | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated people

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "description": null,
      "owner_user_id": null,
      "primary_email": null,
      "primary_phone": null,
      "status": null,
      "lifecycle_stage": null,
      "source": null,
      "next_interaction_at": null,
      "next_interaction_name": null,
      "next_interaction_ref_id": null,
      "next_interaction_icon": null,
      "next_interaction_color": null,
      "organization_id": null,
      "tenant_id": null,
      "created_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/people?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/people`

Create person

Creates a person contact using scoped organization and tenant identifiers.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "description": null,
  "primaryEmail": null,
  "primaryPhone": null,
  "nextInteraction": null,
  "firstName": "string",
  "lastName": "string",
  "linkedInUrl": null,
  "twitterUrl": null,
  "companyEntityId": null
}
```

### Responses

**201** – Person created

Content-Type: `application/json`

```json
{
  "id": null,
  "personId": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/people" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"description\": null,
  \"primaryEmail\": null,
  \"primaryPhone\": null,
  \"nextInteraction\": null,
  \"firstName\": \"string\",
  \"lastName\": \"string\",
  \"linkedInUrl\": null,
  \"twitterUrl\": null,
  \"companyEntityId\": null
}"
```

## PUT `/customers/people`

Update person

Updates contact details or custom fields for a person.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "description": null,
  "primaryEmail": null,
  "primaryPhone": null,
  "nextInteraction": null,
  "linkedInUrl": null,
  "twitterUrl": null,
  "companyEntityId": null
}
```

### Responses

**200** – Person updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/people" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"description\": null,
  \"primaryEmail\": null,
  \"primaryPhone\": null,
  \"nextInteraction\": null,
  \"linkedInUrl\": null,
  \"twitterUrl\": null,
  \"companyEntityId\": null
}"
```

## GET `/customers/people/{id}`

Fetch person with related data

Returns a person customer record with optional related resources such as addresses, comments, activities, interactions, deals, and todos.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| include | query | any | Optional. Comma-separated list of relations to include (addresses, comments, activities, interactions, deals, todos). |

### Responses

**200** – Person detail payload

Content-Type: `application/json`

```json
{
  "interactionMode": "canonical",
  "person": {
    "id": "00000000-0000-4000-8000-000000000000",
    "displayName": null,
    "description": null,
    "ownerUserId": null,
    "primaryEmail": null,
    "primaryPhone": null,
    "status": null,
    "lifecycleStage": null,
    "source": null,
    "nextInteractionAt": null,
    "nextInteractionName": null,
    "nextInteractionRefId": null,
    "nextInteractionIcon": null,
    "nextInteractionColor": null,
    "organizationId": null,
    "tenantId": null,
    "createdAt": "string",
    "updatedAt": "string"
  },
  "profile": null,
  "customFields": {},
  "tags": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "label": "string",
      "color": null
    }
  ],
  "addresses": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": null,
      "purpose": null,
      "addressLine1": null,
      "addressLine2": null,
      "buildingNumber": null,
      "flatNumber": null,
      "city": null,
      "region": null,
      "postalCode": null,
      "country": null,
      "latitude": null,
      "longitude": null,
      "isPrimary": null,
      "createdAt": "string"
    }
  ],
  "comments": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "body": null,
      "authorUserId": null,
      "authorName": null,
      "authorEmail": null,
      "dealId": null,
      "createdAt": "string",
      "appearanceIcon": null,
      "appearanceColor": null
    }
  ],
  "activities": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "activityType": "string",
      "subject": null,
      "body": null,
      "occurredAt": null,
      "dealId": null,
      "authorUserId": null,
      "authorName": null,
      "authorEmail": null,
      "createdAt": "string",
      "appearanceIcon": null,
      "appearanceColor": null
    }
  ],
  "interactions": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "entityId": null,
      "interactionType": "string",
      "title": null,
      "body": null,
      "status": "string",
      "scheduledAt": null,
      "occurredAt": null,
      "priority": null,
      "authorUserId": null,
      "ownerUserId": null,
      "dealId": null,
      "organizationId": null,
      "tenantId": null,
      "authorName": null,
      "authorEmail": null,
      "dealTitle": null,
      "customValues": null,
      "appearanceIcon": null,
      "appearanceColor": null,
      "source": null,
      "createdAt": "string",
      "updatedAt": "string"
    }
  ],
  "deals": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "title": null,
      "status": null,
      "pipelineStage": null,
      "valueAmount": null,
      "valueCurrency": null,
      "probability": null,
      "expectedCloseAt": null,
      "ownerUserId": null,
      "source": null,
      "closureOutcome": null,
      "lossReasonId": null,
      "lossNotes": null,
      "createdAt": "string",
      "updatedAt": "string"
    }
  ],
  "todos": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "todoId": "00000000-0000-4000-8000-000000000000",
      "todoSource": "string",
      "createdAt": "string",
      "createdByUserId": null,
      "title": null,
      "isDone": null,
      "priority": null,
      "severity": null,
      "description": null,
      "dueAt": null,
      "todoOrganizationId": null,
      "customValues": null
    }
  ],
  "isPrimary": true,
  "companies": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "displayName": "string",
      "isPrimary": true
    }
  ],
  "viewer": {
    "userId": null,
    "name": null,
    "email": null
  }
}
```

**400** – Invalid identifier

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden for tenant/organization scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Person not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/people/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/people/{id}/companies`

List linked companies for a person

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Linked company rows

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "companyId": "00000000-0000-4000-8000-000000000000",
      "displayName": "string",
      "isPrimary": true
    }
  ]
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/people/:id/companies" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/people/{id}/companies`

Link a company to a person

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "companyId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**200** – Linked company row

Content-Type: `application/json`

```json
{
  "ok": true,
  "result": {
    "id": "00000000-0000-4000-8000-000000000000",
    "companyId": "00000000-0000-4000-8000-000000000000",
    "displayName": "string",
    "isPrimary": true
  }
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/people/:id/companies" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"companyId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## DELETE `/customers/people/{id}/companies/{linkId}`

Remove a linked company from a person

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| linkId | path | any | Required |

### Responses

**200** – Deletion result

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/people/:id/companies/:linkId" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/customers/people/{id}/companies/{linkId}`

Update a linked company for a person

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| linkId | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Updated company link

Content-Type: `application/json`

```json
{
  "ok": true,
  "result": null
}
```

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/api/customers/people/:id/companies/:linkId" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## GET `/customers/people/{id}/companies/enriched`

Get enriched company data for a person's linked companies

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| sort | query | any | Optional |

### Responses

**200** – Enriched company rows with profile, billing, tags, deals and more

Content-Type: `application/json`

```json
{
  "items": [
    {
      "linkId": "00000000-0000-4000-8000-000000000000",
      "companyId": "00000000-0000-4000-8000-000000000000",
      "displayName": "string",
      "isPrimary": true,
      "subtitle": null,
      "profile": null,
      "billing": null,
      "primaryAddress": null,
      "tags": [
        {
          "id": "00000000-0000-4000-8000-000000000000",
          "label": "string",
          "color": null
        }
      ],
      "roles": [
        {
          "id": "00000000-0000-4000-8000-000000000000",
          "roleValue": "string"
        }
      ],
      "activeDeal": null,
      "lastContactAt": null,
      "clv": null,
      "status": null,
      "lifecycleStage": null,
      "temperature": null,
      "renewalQuarter": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/people/:id/companies/enriched?page=1&pageSize=20&sort=name-asc" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/people/{id}/email-threads`

List a Person's email threads (Gmail-style conversation grouping)

**Tags:** Customers, Email

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Email threads for the person, grouped by conversation

Content-Type: `application/json`

**400** – Invalid person id

Content-Type: `application/json`

**401** – Unauthorized

Content-Type: `application/json`

**403** – Missing customers.people.view feature

Content-Type: `application/json`

**404** – Person not found

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/people/:id/email-threads" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/people/{id}/emails`

Compose + send an email anchored to a Person

**Tags:** Customers, Email

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Email queued for send

Content-Type: `application/json`

**400** – Invalid person id

Content-Type: `application/json`

**401** – Unauthorized

Content-Type: `application/json`

**403** – Missing customers.email.compose feature or mutation guard rejection

Content-Type: `application/json`

**404** – Person or channel not found

Content-Type: `application/json`

**409** – Channel not connected

Content-Type: `application/json`

**422** – Invalid request body

Content-Type: `application/json`

**500** – Send failed

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/people/:id/emails" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/customers/people/{id}/roles`

Remove a person role assignment

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| roleId | query | any | Required |

### Responses

**200** – Role deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/people/00000000-0000-4000-8000-000000000000/roles?roleId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/customers/people/{id}/roles`

List roles for a person

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Role assignments

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "entityType": "company",
      "entityId": "00000000-0000-4000-8000-000000000000",
      "userId": "00000000-0000-4000-8000-000000000000",
      "userName": null,
      "userEmail": null,
      "userPhone": null,
      "roleType": "string",
      "createdAt": "string",
      "updatedAt": "string"
    }
  ]
}
```

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/people/00000000-0000-4000-8000-000000000000/roles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/people/{id}/roles`

Assign a role to a person

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "roleType": "string",
  "userId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**201** – Role created

Content-Type: `application/json`

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

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Role already assigned

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/people/00000000-0000-4000-8000-000000000000/roles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"roleType\": \"string\",
  \"userId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## PUT `/customers/people/{id}/roles`

Update a person role assignment

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| roleId | query | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "userId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**200** – Role updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/people/00000000-0000-4000-8000-000000000000/roles?roleId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"userId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/people/check-phone`

Find person by phone digits

Performs an exact digits comparison (stripping non-numeric characters) to determine whether a customer contact matches the provided phone fragment.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| digits | query | any | Required |

### Responses

**200** – Matching contact (if any)

Content-Type: `application/json`

```json
{
  "match": null
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/people/check-phone" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/customers/pipeline-stages`

Delete pipeline stage

Deletes a pipeline stage. Returns 409 if active deals use this stage.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Stage deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**404** – Stage not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Stage has active deals

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/pipeline-stages" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/pipeline-stages`

List pipeline stages

Returns pipeline stages for the authenticated organization, optionally filtered by pipelineId.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| pipelineId | query | any | Optional |

### Responses

**200** – Stage list

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "pipelineId": "00000000-0000-4000-8000-000000000000",
      "label": "string",
      "order": 1,
      "color": null,
      "icon": null,
      "organizationId": "00000000-0000-4000-8000-000000000000",
      "tenantId": "00000000-0000-4000-8000-000000000000",
      "createdAt": "2025-01-01T00:00:00.000Z",
      "updatedAt": "2025-01-01T00:00:00.000Z"
    }
  ],
  "total": 1
}
```

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/pipeline-stages" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/pipeline-stages`

Create pipeline stage

Creates a new pipeline stage.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "pipelineId": "00000000-0000-4000-8000-000000000000",
  "label": "string",
  "color": null,
  "icon": null
}
```

### Responses

**201** – Stage created

Content-Type: `application/json`

```json
{
  "id": null
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/pipeline-stages" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"pipelineId\": \"00000000-0000-4000-8000-000000000000\",
  \"label\": \"string\",
  \"color\": null,
  \"icon\": null
}"
```

## PUT `/customers/pipeline-stages`

Update pipeline stage

Updates an existing pipeline stage.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "color": null,
  "icon": null
}
```

### Responses

**200** – Stage updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Stage not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/pipeline-stages" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"color\": null,
  \"icon\": null
}"
```

## POST `/customers/pipeline-stages/reorder`

Reorder pipeline stages

Updates the order of pipeline stages in bulk.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "stages": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "order": 1
    }
  ]
}
```

### Responses

**200** – Stages reordered

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/pipeline-stages/reorder" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"stages\": [
    {
      \"id\": \"00000000-0000-4000-8000-000000000000\",
      \"order\": 1
    }
  ]
}"
```

## DELETE `/customers/pipelines`

Delete pipeline

Deletes a pipeline. Returns 409 if active deals exist.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Pipeline deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**404** – Pipeline not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Pipeline has active deals

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/pipelines" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/pipelines`

List pipelines

Returns a list of pipelines scoped to the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| isDefault | query | any | Optional |

### Responses

**200** – Pipeline list

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "isDefault": true,
      "organizationId": "00000000-0000-4000-8000-000000000000",
      "tenantId": "00000000-0000-4000-8000-000000000000",
      "createdAt": "2025-01-01T00:00:00.000Z",
      "updatedAt": "2025-01-01T00:00:00.000Z"
    }
  ],
  "total": 1
}
```

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/pipelines" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/pipelines`

Create pipeline

Creates a new pipeline within the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "name": "string"
}
```

### Responses

**201** – Pipeline created

Content-Type: `application/json`

```json
{
  "id": null
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/pipelines" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\"
}"
```

## PUT `/customers/pipelines`

Update pipeline

Updates an existing pipeline.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Pipeline updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Pipeline not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/pipelines" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/settings/address-format`

Retrieve address format

Returns the current address formatting preference for the selected organization.

**Tags:** Customers

**Requires authentication.**

### Responses

**200** – Current address format

Content-Type: `application/json`

```json
{
  "addressFormat": "string"
}
```

**400** – Organization context missing

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/settings/address-format" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/customers/settings/address-format`

Update address format

Updates the address format preference for the selected organization.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "addressFormat": "line_first"
}
```

### Responses

**200** – Updated address format

Content-Type: `application/json`

```json
{
  "addressFormat": "string"
}
```

**400** – Invalid payload or organization context

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/settings/address-format" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"addressFormat\": \"line_first\"
}"
```

## GET `/customers/settings/dictionary-sort-modes`

Retrieve dictionary sort modes

Returns entry sort preferences for customer dictionaries in the selected organization.

**Tags:** Customers

**Requires authentication.**

### Responses

**200** – Current dictionary sort modes

Content-Type: `application/json`

```json
{
  "dictionarySortModes": {
    "key": "label_asc"
  }
}
```

**400** – Organization context missing

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/settings/dictionary-sort-modes" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/customers/settings/dictionary-sort-modes`

Update dictionary sort modes

Updates entry sort preferences for customer dictionaries in the selected organization.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "dictionarySortModes": {
    "key": "label_asc"
  }
}
```

### Responses

**200** – Updated dictionary sort modes

Content-Type: `application/json`

```json
{
  "dictionarySortModes": {
    "key": "label_asc"
  }
}
```

**400** – Invalid payload or organization context

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/api/customers/settings/dictionary-sort-modes" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"dictionarySortModes\": {
    \"key\": \"label_asc\"
  }
}"
```

## GET `/customers/settings/stuck-threshold`

Retrieve stuck-threshold days

Returns the current stuck-deal threshold (in days) for the selected organization.

**Tags:** Customers

**Requires authentication.**

### Responses

**200** – Current threshold

Content-Type: `application/json`

```json
{
  "stuckThresholdDays": 1
}
```

**400** – Organization context missing

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/settings/stuck-threshold" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/customers/settings/stuck-threshold`

Update stuck-threshold days

Updates the stuck-deal threshold for the selected organization.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "stuckThresholdDays": 1
}
```

### Responses

**200** – Updated threshold

Content-Type: `application/json`

```json
{
  "stuckThresholdDays": 1
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/settings/stuck-threshold" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"stuckThresholdDays\": 1
}"
```

## DELETE `/customers/tags`

Delete tag

Deletes a tag identified by `id`. The identifier may be provided via body or query string.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Tag deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/tags" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/tags`

List tags

Returns a paginated collection of tags scoped to the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated tags

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "slug": "string",
      "label": "string",
      "color": null,
      "description": null,
      "organization_id": null,
      "tenant_id": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/tags?page=1&pageSize=100" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/tags`

Create tag

Creates a tag scoped to the current tenant and organization.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "slug": "string",
  "label": "string"
}
```

### Responses

**201** – Tag created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/tags" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"slug\": \"string\",
  \"label\": \"string\"
}"
```

## PUT `/customers/tags`

Update tag

Updates label, color, or description for an existing tag.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Tag updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/tags" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/customers/tags/assign`

Assign tag to customer entity

Links a tag to a customer entity within the validated tenant / organization scope.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "tagId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**201** – Tag assigned to customer

Content-Type: `application/json`

```json
{
  "id": null
}
```

**400** – Validation or assignment failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Insufficient tenant/organization access

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/tags/assign" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"tagId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/customers/tags/unassign`

Remove tag from customer entity

Detaches a tag from a customer entity within the validated tenant / organization scope.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "tagId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**200** – Tag unassigned from customer

Content-Type: `application/json`

```json
{
  "id": null
}
```

**400** – Validation or unassignment failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Insufficient tenant/organization access

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/tags/unassign" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"tagId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## DELETE `/customers/todos`

Delete customertodo

DEPRECATED (sunset 2026-06-30): Deletes a customer task. Use DELETE /api/customers/interactions instead.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – CustomerTodo deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/customers/todos" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/customers/todos`

List customertodos

Returns a paginated collection of customertodos scoped to the authenticated organization.

**Tags:** Customers

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| all | query | any | Optional |
| entityId | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated customertodos

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "string",
      "todoId": "string",
      "todoSource": "string",
      "todoTitle": null,
      "todoIsDone": null,
      "todoPriority": null,
      "todoSeverity": null,
      "todoDescription": null,
      "todoDueAt": null,
      "todoCustomValues": null,
      "todoOrganizationId": null,
      "todoUpdatedAt": null,
      "organizationId": "string",
      "tenantId": "string",
      "createdAt": "string",
      "externalHref": null,
      "customer": {
        "id": null,
        "displayName": null,
        "kind": null
      }
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/customers/todos?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/customers/todos`

Create customertodo

DEPRECATED (sunset 2026-06-30): Creates a customer task. Use POST /api/customers/interactions instead.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "00000000-0000-4000-8000-000000000000",
  "title": "string",
  "todoSource": "customers:interaction"
}
```

### Responses

**201** – CustomerTodo created

Content-Type: `application/json`

```json
{
  "linkId": null,
  "todoId": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/customers/todos" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"00000000-0000-4000-8000-000000000000\",
  \"title\": \"string\",
  \"todoSource\": \"customers:interaction\"
}"
```

## PUT `/customers/todos`

Update customertodo

DEPRECATED (sunset 2026-06-30): Updates a customer task. Use PUT /api/customers/interactions instead.

**Tags:** Customers

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – CustomerTodo updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/customers/todos" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/dashboards/layout`

Load the current dashboard layout

Returns the saved widget layout together with the widgets the current user is allowed to place.

**Tags:** Dashboards

**Requires authentication.**

### Responses

**200** – Current dashboard layout and available widgets.

Content-Type: `application/json`

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

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/dashboards/layout" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/dashboards/layout`

Persist dashboard layout changes

Saves the provided widget ordering, sizes, and settings for the current user.

**Tags:** Dashboards

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "widgetId": "string",
      "order": 1
    }
  ]
}
```

### Responses

**200** – Layout updated successfully.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid layout payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing dashboards.configure feature

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**503** – Widget registry unavailable — the layout was not saved

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/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}`

Update a dashboard layout item

Adjusts the size or settings for a single widget within the dashboard layout.

**Tags:** Dashboards

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| itemId | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Layout item updated.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid payload or missing item id

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing dashboards.configure feature

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Item not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/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`

Fetch widget assignments for a role

Returns the widgets explicitly assigned to the given role together with the evaluation scope.

**Tags:** Dashboards

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| roleId | query | any | Required |
| tenantId | query | any | Optional |
| organizationId | query | any | Optional |

### Responses

**200** – Current widget configuration for the role.

Content-Type: `application/json`

```json
{
  "widgetIds": [
    "string"
  ],
  "hasCustom": true,
  "scope": {
    "tenantId": null,
    "organizationId": null
  }
}
```

**400** – Missing role identifier

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Insufficient permissions to manage role widgets

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/dashboards/roles/widgets?roleId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/dashboards/roles/widgets`

Update widgets assigned to a role

Persists the widget list for a role within the provided tenant and organization scope.

**Tags:** Dashboards

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "roleId": "00000000-0000-4000-8000-000000000000",
  "widgetIds": [
    "string"
  ]
}
```

### Responses

**200** – Widgets updated successfully.

Content-Type: `application/json`

```json
{
  "ok": true,
  "widgetIds": [
    "string"
  ]
}
```

**400** – Invalid payload or unknown widgets

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Insufficient permissions to manage role widgets

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/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`

Read widget overrides for a user

Returns the widgets inherited and explicitly configured for the requested user within the current scope.

**Tags:** Dashboards

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| userId | query | any | Required |
| tenantId | query | any | Optional |
| organizationId | query | any | Optional |

### Responses

**200** – Widget settings for the user.

Content-Type: `application/json`

```json
{
  "mode": "inherit",
  "widgetIds": [
    "string"
  ],
  "hasCustom": true,
  "effectiveWidgetIds": [
    "string"
  ],
  "scope": {
    "tenantId": null,
    "organizationId": null
  }
}
```

**400** – Missing user identifier

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Insufficient permissions to manage user widgets

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/dashboards/users/widgets?userId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/dashboards/users/widgets`

Update user-specific dashboard widgets

Sets the widget override mode and allowed widgets for a user. Passing `mode: inherit` clears overrides.

**Tags:** Dashboards

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "userId": "00000000-0000-4000-8000-000000000000",
  "mode": "inherit",
  "widgetIds": [
    "string"
  ]
}
```

### Responses

**200** – Overrides saved.

Content-Type: `application/json`

```json
{
  "ok": true,
  "mode": "inherit",
  "widgetIds": [
    "string"
  ]
}
```

**400** – Invalid payload or unknown widgets

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Insufficient permissions to manage user widgets

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/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`

List available dashboard widgets

Returns the catalog of widgets that modules expose, including defaults and feature requirements.

**Tags:** Dashboards

**Requires authentication.**

### Responses

**200** – Widgets available for assignment.

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "string",
      "title": "string",
      "description": null,
      "defaultSize": "sm",
      "defaultEnabled": true,
      "defaultSettings": null,
      "features": [
        "string"
      ],
      "moduleId": "string",
      "icon": null,
      "loaderKey": "string",
      "supportsRefresh": true
    }
  ]
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Insufficient permissions to view widget catalog

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/dashboards/widgets/catalog" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/dashboards/widgets/data`

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.

**Tags:** Dashboards

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityType": "string",
  "metric": {
    "field": "string",
    "aggregate": "count"
  }
}
```

### Responses

**200** – Aggregated data for the widget.

Content-Type: `application/json`

```json
{
  "value": null,
  "data": [
    {
      "value": null
    }
  ],
  "metadata": {
    "fetchedAt": "string",
    "recordCount": 1,
    "currency": null
  }
}
```

**400** – Invalid request payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing analytics.view feature

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**422** – Too many rows to group an encrypted field in application code

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Internal server error

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**503** – Encryption is configured but the group source cannot currently be resolved

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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`

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.

**Tags:** Dashboards

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "requests": [
    {
      "id": "string",
      "request": {
        "entityType": "string",
        "metric": {
          "field": "string",
          "aggregate": "count"
        }
      }
    }
  ]
}
```

### Responses

**200** – Per-widget aggregation results keyed by request id.

Content-Type: `application/json`

```json
{
  "results": [
    {
      "id": "string",
      "ok": true,
      "data": {
        "value": null,
        "data": [
          {
            "value": null
          }
        ],
        "metadata": {
          "fetchedAt": "string",
          "recordCount": 1,
          "currency": null
        }
      }
    }
  ]
}
```

**400** – Invalid request payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Internal server error

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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\"
        }
      }
    }
  ]
}"
```

## GET `/dictionaries`

List dictionaries

Returns dictionaries accessible to the current organization, optionally including inactive records.

**Tags:** Dictionaries

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| includeInactive | query | any | Optional |

### Responses

**200** – Dictionary collection.

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "key": "string",
      "name": "string",
      "description": null,
      "isSystem": true,
      "isActive": true,
      "managerVisibility": null,
      "organizationId": null,
      "createdAt": "string",
      "updatedAt": null
    }
  ]
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to load dictionaries

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/dictionaries" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/dictionaries`

Create dictionary

Registers a dictionary scoped to the current organization.

**Tags:** Dictionaries

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "key": "string",
  "name": "string"
}
```

### Responses

**201** – Dictionary created.

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "key": "string",
  "name": "string",
  "description": null,
  "isSystem": true,
  "isActive": true,
  "managerVisibility": null,
  "organizationId": null,
  "createdAt": "string",
  "updatedAt": null
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Dictionary key already exists

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to create dictionary

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/dictionaries" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"key\": \"string\",
  \"name\": \"string\"
}"
```

## DELETE `/dictionaries/{dictionaryId}`

Delete dictionary

Soft deletes the dictionary unless it is the protected currency dictionary.

**Tags:** Dictionaries

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| dictionaryId | path | any | Required |

### Responses

**200** – Dictionary archived.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Protected dictionary cannot be deleted

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Dictionary not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to delete dictionary

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/dictionaries/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/dictionaries/{dictionaryId}`

Get dictionary

Returns details for the specified dictionary, including inheritance flags.

**Tags:** Dictionaries

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| dictionaryId | path | any | Required |

### Responses

**200** – Dictionary details.

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "key": "string",
  "name": "string",
  "description": null,
  "isSystem": true,
  "isActive": true,
  "managerVisibility": null,
  "organizationId": null,
  "createdAt": "string",
  "updatedAt": null
}
```

**400** – Invalid parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Dictionary not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to load dictionary

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/dictionaries/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/dictionaries/{dictionaryId}`

Update dictionary

Updates mutable attributes of the dictionary. Currency dictionaries are protected from modification.

**Tags:** Dictionaries

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| dictionaryId | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Dictionary updated.

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "key": "string",
  "name": "string",
  "description": null,
  "isSystem": true,
  "isActive": true,
  "managerVisibility": null,
  "organizationId": null,
  "createdAt": "string",
  "updatedAt": null
}
```

**400** – Validation failed or protected dictionary

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Dictionary not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Dictionary key already exists

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to update dictionary

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/api/dictionaries/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## GET `/dictionaries/{dictionaryId}/entries`

List dictionary entries

Returns entries for the specified dictionary ordered by its configured entry sort mode. The number of entries returned per request is capped at 500; use `limit` and `offset` with the `total`/`hasMore` response fields to page through larger dictionaries.

**Tags:** Dictionaries

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| dictionaryId | path | any | Required |
| limit | query | any | Optional. Maximum entries to return. Defaults to and is capped at 500. |
| offset | query | any | Optional. Number of entries to skip. Defaults to 0. |

### Responses

**200** – Dictionary entries.

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "value": "string",
      "label": "string",
      "color": null,
      "icon": null,
      "position": 1,
      "isDefault": true,
      "createdAt": "string",
      "updatedAt": null
    }
  ],
  "total": 1,
  "limit": 1,
  "offset": 1,
  "hasMore": true,
  "sortMode": "label_asc"
}
```

**400** – Invalid parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Dictionary not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to load dictionary entries

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/dictionaries/00000000-0000-4000-8000-000000000000/entries" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/dictionaries/{dictionaryId}/entries`

Create dictionary entry

Creates a new entry in the specified dictionary.

**Tags:** Dictionaries

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| dictionaryId | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "value": "string",
  "color": null,
  "icon": null
}
```

### Responses

**201** – Dictionary entry created.

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "value": "string",
  "label": "string",
  "color": null,
  "icon": null,
  "position": 1,
  "isDefault": true,
  "createdAt": "string",
  "updatedAt": null
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Dictionary not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to create dictionary entry

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/dictionaries/00000000-0000-4000-8000-000000000000/entries" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"value\": \"string\",
  \"color\": null,
  \"icon\": null
}"
```

## DELETE `/dictionaries/{dictionaryId}/entries/{entryId}`

Delete dictionary entry

Deletes the specified dictionary entry via the command bus.

**Tags:** Dictionaries

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| dictionaryId | path | any | Required |
| entryId | path | any | Required |

### Responses

**200** – Entry deleted.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Dictionary or entry not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to delete entry

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/dictionaries/00000000-0000-4000-8000-000000000000/entries/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/dictionaries/{dictionaryId}/entries/{entryId}`

Update dictionary entry

Updates the specified dictionary entry using the command bus pipeline.

**Tags:** Dictionaries

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| dictionaryId | path | any | Required |
| entryId | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "color": null,
  "icon": null
}
```

### Responses

**200** – Dictionary entry updated.

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "value": "string",
  "label": "string",
  "color": null,
  "icon": null,
  "position": 1,
  "isDefault": true,
  "createdAt": "string",
  "updatedAt": null
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Dictionary or entry not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to update entry

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/api/dictionaries/00000000-0000-4000-8000-000000000000/entries/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"color\": null,
  \"icon\": null
}"
```

## POST `/dictionaries/{dictionaryId}/entries/reorder`

Reorder dictionary entries

Updates the position of dictionary entries for drag-and-drop reordering.

**Tags:** Dictionaries

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| dictionaryId | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "entries": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "position": 1
    }
  ]
}
```

### Responses

**200** – Entries reordered.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Dictionary not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to reorder entries

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/dictionaries/00000000-0000-4000-8000-000000000000/entries/reorder" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entries\": [
    {
      \"id\": \"00000000-0000-4000-8000-000000000000\",
      \"position\": 1
    }
  ]
}"
```

## POST `/dictionaries/{dictionaryId}/entries/set-default`

Set default dictionary entry

Marks the specified entry as the default for this dictionary, clearing any previous default.

**Tags:** Dictionaries

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| dictionaryId | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "entryId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**200** – Default entry set.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Dictionary or entry not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Failed to set default entry

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/dictionaries/00000000-0000-4000-8000-000000000000/entries/set-default" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entryId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/directory/organization-branding`

Read sidebar branding for the selected organization

Returns the logo URL used by the backend sidebar for the currently selected organization.

**Tags:** Directory

**Requires authentication.**

### Responses

**200** – Organization branding

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "organizationName": "string",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "logoUrl": null,
  "logoPreserveAspectRatio": true,
  "updatedAt": null
}
```

**400** – A concrete organization scope is required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Organization not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/directory/organization-branding" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/directory/organization-branding`

Update sidebar branding for the selected organization

Stores an external image URL or an internal attachment image URL as the selected organization logo.

**Tags:** Directory

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "logoUrl": null
}
```

### Responses

**200** – Updated organization branding

Content-Type: `application/json`

```json
{
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "organizationName": "string",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "logoUrl": null,
  "logoPreserveAspectRatio": true,
  "updatedAt": null
}
```

**400** – Save failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Organization branding changed since it was loaded

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**422** – Invalid logo URL

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/directory/organization-branding" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"logoUrl\": null
}"
```

## GET `/directory/organization-switcher`

Load organization switcher menu

Returns the hierarchical menu of organizations the current user may switch to within the active tenant.

**Tags:** Directory

**Requires authentication.**

### Responses

**200** – Organization switcher payload.

Content-Type: `application/json`

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

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/directory/organization-switcher" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/directory/organizations`

Delete organization

Soft deletes an organization identified by id.

**Tags:** Directory

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Organization deleted.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing directory.organizations.manage feature

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/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/organizations`

List organizations

Returns organizations using options, tree, or paginated manage view depending on the `view` parameter.

**Tags:** Directory

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| view | query | any | Optional |
| ids | query | any | Optional |
| tenantId | query | any | Optional |
| includeInactive | query | any | Optional |
| status | query | any | Optional |

### Responses

**200** – Organization data for the requested view.

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "parentId": null,
      "parentName": null,
      "tenantId": null,
      "tenantName": null,
      "rootId": null,
      "treePath": null
    }
  ]
}
```

**400** – Invalid query or tenant scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/directory/organizations?page=1&pageSize=50&view=options" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/directory/organizations`

Create organization

Creates a new organization within a tenant and optionally assigns hierarchy relationships.

**Tags:** Directory

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "slug": null,
  "logoUrl": null,
  "parentId": null
}
```

### Responses

**201** – Organization created.

Content-Type: `application/json`

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

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing directory.organizations.manage feature

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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`

Update organization

Updates organization details and hierarchy assignments.

**Tags:** Directory

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "slug": null,
  "logoUrl": null,
  "parentId": null
}
```

### Responses

**200** – Organization updated.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing directory.organizations.manage feature

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/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
}"
```

## GET `/directory/organizations/lookup`

Public organization lookup by slug

**Tags:** Directory (Tenants & Organizations)

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/directory/organizations/lookup" \
  -H "Accept: application/json"
```

## DELETE `/directory/tenants`

Delete tenant

Soft deletes the tenant identified by id.

**Tags:** Directory

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Tenant removed.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing directory.tenants.manage feature

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/directory/tenants" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/directory/tenants`

List tenants

Returns tenants visible to the current user with optional search and pagination.

**Tags:** Directory

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| isActive | query | any | Optional |

### Responses

**200** – Paged list of tenants.

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "isActive": true,
      "createdAt": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
```

**400** – Invalid query parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Requires super-admin

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/directory/tenants?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/directory/tenants`

Create tenant

Creates a new tenant and returns its identifier.

**Tags:** Directory

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string"
}
```

### Responses

**201** – Tenant created.

Content-Type: `application/json`

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

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing directory.tenants.manage feature

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/directory/tenants" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\"
}"
```

## PUT `/directory/tenants`

Update tenant

Updates tenant properties such as name or activation state.

**Tags:** Directory

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Tenant updated.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing directory.tenants.manage feature

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/directory/tenants" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/directory/tenants/lookup`

Public tenant lookup

**Tags:** Directory (Tenants & Organizations)

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/directory/tenants/lookup" \
  -H "Accept: application/json"
```

## DELETE `/entities/definitions`

Soft delete custom field definition

Marks the specified definition inactive and tombstones it for the current scope.

**Tags:** Entities

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "string",
  "key": "string"
}
```

### Responses

**200** – Definition deleted

Content-Type: `application/json`

```json
{
  "ok": true,
  "version": null
}
```

**400** – Missing entity id or key

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Definition not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/entities/definitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"key\": \"string\"
}"
```

## GET `/entities/definitions`

List active custom field definitions

Returns active custom field definitions for the supplied entity ids, respecting tenant scope and tombstones.

**Tags:** Entities

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| entityId | query | any | Optional |
| entityIds | query | any | Optional |
| fieldset | query | any | Optional |

### Responses

**200** – Definition list

Content-Type: `application/json`

```json
{
  "items": [
    {
      "key": "string",
      "kind": "string",
      "label": "string",
      "entityId": "string"
    }
  ]
}
```

**400** – Missing entity id

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/entities/definitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/entities/definitions`

Upsert custom field definition

Creates or updates a custom field definition for the current tenant/org scope.

**Tags:** Entities

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "string",
  "key": "string",
  "kind": "text"
}
```

### Responses

**200** – Definition saved

Content-Type: `application/json`

```json
{
  "ok": true,
  "item": {
    "id": "00000000-0000-4000-8000-000000000000",
    "key": "string",
    "kind": "string",
    "configJson": {}
  }
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/entities/definitions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"key\": \"string\",
  \"kind\": \"text\"
}"
```

## POST `/entities/definitions.batch`

Save multiple custom field definitions

Creates or updates multiple definitions for a single entity in one transaction.

**Tags:** Entities

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "string",
  "definitions": [
    {
      "key": "string",
      "kind": "text"
    }
  ]
}
```

### Responses

**200** – Definitions saved

Content-Type: `application/json`

```json
{
  "ok": true,
  "version": null
}
```

**400** – Validation error

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Unexpected failure

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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`

Get management snapshot

Returns scoped custom field definitions (including inactive tombstones) for administration interfaces.

**Tags:** Entities

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| entityId | query | any | Required |

### Responses

**200** – Scoped definitions and deleted keys

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "key": "string",
      "kind": "string",
      "configJson": null,
      "organizationId": null,
      "tenantId": null
    }
  ],
  "deletedKeys": [
    "string"
  ],
  "version": null
}
```

**400** – Missing entity id

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication or feature

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/entities/definitions.manage?entityId=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/entities/definitions.restore`

Restore definition

Reactivates a previously soft-deleted definition within the current tenant/org scope.

**Tags:** Entities

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "string",
  "key": "string"
}
```

### Responses

**200** – Definition restored

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Missing entity id or key

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Definition not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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`

Fetch encryption map

Returns the encrypted field map for the current tenant/organization scope.

**Tags:** Entities

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| entityId | query | any | Required |

### Responses

**200** – Map

Content-Type: `application/json`

```json
{
  "entityId": "string",
  "fields": [
    {
      "field": "string",
      "hashField": null
    }
  ],
  "updatedAt": null
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/entities/encryption?entityId=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/entities/encryption`

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.

**Tags:** Entities

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "string",
  "tenantId": null,
  "organizationId": null,
  "fields": [
    {
      "field": "string",
      "hashField": null
    }
  ]
}
```

### Responses

**200** – Saved

Content-Type: `application/json`

```json
{
  "ok": true,
  "updatedAt": null
}
```

**409** – Optimistic-lock conflict (stale write)

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "currentUpdatedAt": "string",
  "expectedUpdatedAt": "string"
}
```

**422** – Selected organization is unavailable

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "organization_selection_invalid"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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
    }
  ]
}"
```

## DELETE `/entities/entities`

Soft delete custom entity

Marks the specified custom entity inactive within the current scope.

**Tags:** Entities

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "string"
}
```

### Responses

**200** – Entity deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Missing entity id

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Entity not found in scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/entities/entities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\"
}"
```

## GET `/entities/entities`

List available entities

Returns generated and custom entities scoped to the caller with field counts per entity.

**Tags:** Entities

**Requires authentication.**

### Responses

**200** – List of entities

Content-Type: `application/json`

```json
{
  "items": [
    {
      "entityId": "string",
      "source": "code",
      "label": "string",
      "count": 1
    }
  ]
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/entities/entities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/entities/entities`

Upsert custom entity

Creates or updates a tenant/org scoped custom entity definition.

**Tags:** Entities

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "string",
  "label": "string",
  "description": null,
  "showInSidebar": false,
  "accessRestricted": false
}
```

### Responses

**200** – Entity saved

Content-Type: `application/json`

```json
{
  "ok": true,
  "item": {
    "id": "00000000-0000-4000-8000-000000000000",
    "entityId": "string",
    "label": "string"
  }
}
```

**400** – Validation error

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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
}"
```

## GET `/entities/entity-settings`

Get custom entity settings

Returns the tenant-scoped default-restricted policy for new custom entities.

**Tags:** Entities

**Requires authentication.**

### Responses

**200** – Current settings

Content-Type: `application/json`

```json
{
  "newEntitiesRestrictedByDefault": true,
  "updatedAt": null
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/entities/entity-settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/entities/entity-settings`

Update custom entity settings

Sets the tenant-scoped default-restricted policy for new custom entities.

**Tags:** Entities

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "newEntitiesRestrictedByDefault": true
}
```

### Responses

**200** – Updated settings

Content-Type: `application/json`

```json
{
  "ok": true,
  "newEntitiesRestrictedByDefault": true,
  "updatedAt": null
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Optimistic lock conflict

Content-Type: `application/json`

```json
{
  "code": "string",
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/entities/entity-settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"newEntitiesRestrictedByDefault\": true
}"
```

## DELETE `/entities/records`

Delete record

Soft deletes the specified record within the current tenant/org scope.

**Tags:** Entities

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "string",
  "recordId": "string"
}
```

### Responses

**200** – Record deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Missing entity id or record id

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Record not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Unexpected failure

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/entities/records" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"recordId\": \"string\"
}"
```

## GET `/entities/records`

List records

Returns paginated records for the supplied entity. Supports custom field filters, exports, and soft-delete toggles.

**Tags:** Entities

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| entityId | query | any | Required |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| search | query | any | Optional |
| searchFields | query | any | Optional |
| withDeleted | query | any | Optional |
| format | query | any | Optional |
| exportScope | query | any | Optional |
| export_scope | query | any | Optional |
| all | query | any | Optional |
| full | query | any | Optional |

### Responses

**200** – Paginated records

Content-Type: `application/json`

```json
{
  "items": [
    {}
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
```

**400** – Missing entity id

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Unexpected failure

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/entities/records?entityId=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/entities/records`

Create record

Creates a record for the given entity. When `recordId` is omitted or not a UUID the data engine will generate one automatically.

**Tags:** Entities

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "string",
  "values": {}
}
```

### Responses

**200** – Record created

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failure

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Unexpected failure

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/entities/records" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"values\": {}
}"
```

## PUT `/entities/records`

Update record

Updates an existing record. If the provided recordId is not a UUID the record will be created instead to support optimistic flows.

**Tags:** Entities

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityId": "string",
  "recordId": "string",
  "values": {}
}
```

### Responses

**200** – Record updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Validation failure

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Unexpected failure

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/entities/records" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityId\": \"string\",
  \"recordId\": \"string\",
  \"values\": {}
}"
```

## GET `/entities/relations/options`

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.

**Tags:** Entities

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| entityId | query | any | Required |
| labelField | query | any | Optional |
| q | query | any | Optional |
| ids | query | any | Optional |
| routeContextFields | query | any | Optional |

### Responses

**200** – Option list

Content-Type: `application/json`

```json
{
  "items": [
    {
      "value": "string",
      "label": "string"
    }
  ]
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/entities/relations/options?entityId=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/entities/sidebar-entities`

Get sidebar entities

Returns custom entities flagged with `showInSidebar` for the current tenant/org scope.

**Tags:** Entities

**Requires authentication.**

### Responses

**200** – Sidebar entities for navigation

Content-Type: `application/json`

```json
{
  "items": [
    {
      "entityId": "string",
      "label": "string",
      "href": "string"
    }
  ]
}
```

**401** – Missing authentication

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/entities/sidebar-entities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/events`

List declared events

Returns every declared event. Filters: category, module, excludeTriggerExcluded (default true).

**Tags:** Events

**Requires authentication.**

### Responses

**200** – Declared events

Content-Type: `application/json`

```json
{
  "data": [
    {
      "id": "string",
      "label": "string"
    }
  ],
  "total": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/events" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/events/stream`

GET /events/stream

**Tags:** Events

**Requires authentication.**

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/events/stream" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/feature_toggles/check/boolean`

Check if feature is enabled

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

**Tags:** Feature Toggles

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| identifier | query | any | Required. Feature toggle identifier |

### Responses

**200** – Feature status

Content-Type: `application/json`

```json
{
  "enabled": true,
  "source": "override",
  "toggleId": "string",
  "identifier": "string",
  "tenantId": "string"
}
```

**400** – Bad Request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Tenant not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/feature_toggles/check/boolean?identifier=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/feature_toggles/check/json`

Get json config

Gets the json configuration for a feature toggle.

**Tags:** Feature Toggles

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| identifier | query | any | Required. Feature toggle identifier |

### Responses

**200** – Json config

Content-Type: `application/json`

```json
{
  "valueType": "json",
  "source": "override",
  "toggleId": "string",
  "identifier": "string",
  "tenantId": "string"
}
```

**400** – Bad Request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Tenant not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/feature_toggles/check/json?identifier=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/feature_toggles/check/number`

Get number config

Gets the number configuration for a feature toggle.

**Tags:** Feature Toggles

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| identifier | query | any | Required. Feature toggle identifier |

### Responses

**200** – Number config

Content-Type: `application/json`

```json
{
  "valueType": "number",
  "value": 1,
  "source": "override",
  "toggleId": "string",
  "identifier": "string",
  "tenantId": "string"
}
```

**400** – Bad Request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Tenant not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/feature_toggles/check/number?identifier=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/feature_toggles/check/string`

Get string config

Gets the string configuration for a feature toggle.

**Tags:** Feature Toggles

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| identifier | query | any | Required. Feature toggle identifier |

### Responses

**200** – String config

Content-Type: `application/json`

```json
{
  "valueType": "string",
  "value": "string",
  "source": "override",
  "toggleId": "string",
  "identifier": "string",
  "tenantId": "string"
}
```

**400** – Bad Request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Tenant not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/feature_toggles/check/string?identifier=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/feature_toggles/global`

Delete global feature toggle

Soft deletes a global feature toggle by ID. Requires superadmin role.

**Tags:** Feature Toggles

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Required. Feature toggle identifier |

### Responses

**200** – Feature toggle deleted

Content-Type: `application/json`

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

**400** – Invalid identifier

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden - superadmin role required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Feature toggle not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/feature_toggles/global?id=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/feature_toggles/global`

List global feature toggles

Returns all global feature toggles with filtering and pagination. Requires superadmin role.

**Tags:** Feature Toggles

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional. Page number for pagination |
| pageSize | query | any | Optional. Number of items per page (max 200) |
| search | query | any | Optional. Case-insensitive search across identifier, name, description, and category |
| type | query | any | Optional. Filter by toggle type (boolean, string, number, json) |
| category | query | any | Optional. Filter by category (case-insensitive partial match) |
| name | query | any | Optional. Filter by name (case-insensitive partial match) |
| identifier | query | any | Optional. Filter by identifier (case-insensitive partial match) |
| sortField | query | any | Optional. Field to sort by |
| sortDir | query | any | Optional. Sort direction (ascending or descending) |

### Responses

**200** – Feature toggles collection

Content-Type: `application/json`

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

**400** – Invalid query parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden - superadmin role required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/feature_toggles/global?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/feature_toggles/global`

Create global feature toggle

Creates a new global feature toggle. Requires superadmin role.

**Tags:** Feature Toggles

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**201** – Feature toggle created

Content-Type: `application/json`

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

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden - superadmin role required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/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`

Update global feature toggle

Updates an existing global feature toggle. Requires superadmin role.

**Tags:** Feature Toggles

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Feature toggle updated

Content-Type: `application/json`

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

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden - superadmin role required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Feature toggle not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/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
}"
```

## GET `/feature_toggles/global/{id}`

Fetch feature toggle by ID

Returns complete details of a feature toggle.

**Tags:** Feature Toggles

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Feature toggle detail

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "identifier": "string",
  "name": "string",
  "description": null,
  "category": null,
  "type": "boolean",
  "defaultValue": null,
  "createdAt": null,
  "updatedAt": null
}
```

**400** – Invalid identifier

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Feature toggle not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/feature_toggles/global/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/feature_toggles/global/{id}/override`

Fetch feature toggle override

Returns feature toggle override.

**Tags:** Feature Toggles

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Feature toggle overrides

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "tenantName": "string",
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "toggleType": "boolean",
  "updatedAt": null
}
```

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Feature toggle not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/feature_toggles/global/:id/override" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/feature_toggles/overrides`

List overrides

Returns list of feature toggle overrides.

**Tags:** Feature Toggles

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| category | query | any | Optional |
| name | query | any | Optional |
| identifier | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |

### Responses

**200** – List of overrides

Content-Type: `application/json`

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

**400** – Invalid query parameters

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/feature_toggles/overrides?page=1&pageSize=25" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/feature_toggles/overrides`

Change override state

Enable, disable or inherit a feature toggle for a specific tenant.

**Tags:** Feature Toggles

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Override updated

Content-Type: `application/json`

```json
{
  "ok": true,
  "overrideToggleId": null
}
```

**400** – Validation failed

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Internal server error

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/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
}"
```

## POST `/hoopi_billing/adjustments`

Umarza brakującą resztę Należności

Korekta jest **dopisywalna, nie edytowalna i nie cofalna** — pomyłkę prostuje kolejny wpis. Status Należności przeliczany w tej samej transakcji.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "type": "credit",
  "chargeId": "00000000-0000-4000-8000-000000000000",
  "amountMinor": "string",
  "currency": "string",
  "reason": "string"
}
```

### Responses

**200** – Korekta zapisana

Content-Type: `application/json`

```json
{
  "adjustmentId": "00000000-0000-4000-8000-000000000000",
  "type": "string",
  "amountMinor": "string",
  "chargeId": null,
  "chargeStatus": null,
  "tenantId": null,
  "organizationId": null
}
```

**400** – Niepoprawne wejście albo typ inny niż `credit`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Należność nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Należność anulowana, inna waluta, albo kwota większa niż brakująca reszta

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/adjustments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"type\": \"credit\",
  \"chargeId\": \"00000000-0000-4000-8000-000000000000\",
  \"amountMinor\": \"string\",
  \"currency\": \"string\",
  \"reason\": \"string\"
}"
```

## POST `/hoopi_billing/adjustments/discount`

Przyznaje zniżkę z katalogu na istniejącą Należność — na podstawie wpisanego Uprawnienia

Kwotę liczy reguła obowiązująca na `period_from` Należności (procent albo kwota stała, nie więcej niż kwota Należności). Najwyżej jedna zniżka na Należność; zniżka nie może przekroczyć brakującej kwoty. `dryRun` oddaje podgląd bez zapisu.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "chargeId": "00000000-0000-4000-8000-000000000000",
  "ruleId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**200** – Podgląd (`dryRun`)

Content-Type: `application/json`

```json
{
  "dryRun": true,
  "adjustmentId": null,
  "chargeId": "00000000-0000-4000-8000-000000000000",
  "ruleId": "00000000-0000-4000-8000-000000000000",
  "ruleName": "string",
  "ruleValue": "string",
  "amountMinor": "string",
  "eligibilityId": null,
  "eligibilityKind": null,
  "eligibilityValidUntil": null,
  "siblingPosition": null,
  "chargeStatus": "string",
  "missingMinor": "string",
  "tenantId": "string",
  "organizationId": "string"
}
```

**201** – Zniżka zapisana

Content-Type: `application/json`

```json
{
  "dryRun": true,
  "adjustmentId": null,
  "chargeId": "00000000-0000-4000-8000-000000000000",
  "ruleId": "00000000-0000-4000-8000-000000000000",
  "ruleName": "string",
  "ruleValue": "string",
  "amountMinor": "string",
  "eligibilityId": null,
  "eligibilityKind": null,
  "eligibilityValidUntil": null,
  "siblingPosition": null,
  "chargeStatus": "string",
  "missingMinor": "string",
  "tenantId": "string",
  "organizationId": "string"
}
```

**400** – Niepoprawne wejście albo brak autora w kontekście (`actor_required`)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy `hoopi_billing.discounts.grant`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Należność albo reguła nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – `charge_cancelled`, `discount_exists`, `discount_exceeds_missing`, `currency_mismatch`, `rule_not_effective`, `eligibility_missing`, `discount_zero`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/adjustments/discount" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"chargeId\": \"00000000-0000-4000-8000-000000000000\",
  \"ruleId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/hoopi_billing/adjustments/refund`

Oddaje wolne środki albo część opłaconej Należności

Przy zwrocie części Należności alokacja maleje **przed** podniesieniem `refunded_amount_minor`, a Należność wraca na `partially_paid`; przy zwrocie całości dostaje `refunded` i **nie wraca** do propozycji alokacji.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "paymentId": "00000000-0000-4000-8000-000000000000",
  "amountMinor": "string",
  "currency": "string",
  "reason": "string"
}
```

### Responses

**200** – Zwrot zapisany

Content-Type: `application/json`

```json
{
  "adjustmentId": "00000000-0000-4000-8000-000000000000",
  "type": "string",
  "amountMinor": "string",
  "chargeId": null,
  "chargeStatus": null,
  "tenantId": null,
  "organizationId": null
}
```

**400** – Niepoprawne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Wpłata albo Należność nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Wpłata cofnięta, inna waluta, kwota większa niż alokacja albo niż wolne środki, albo brak alokacji do zmniejszenia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/adjustments/refund" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"paymentId\": \"00000000-0000-4000-8000-000000000000\",
  \"amountMinor\": \"string\",
  \"currency\": \"string\",
  \"reason\": \"string\"
}"
```

## GET `/hoopi_billing/balances`

Do zapłaty, zaległość i wolne środki w rozbiciu na płatników

Trzy liczby, nie jedna: „saldo netto" nie istnieje świadomie, bo spec i słownik podawały dwa przeciwne znaki tego samego pojęcia. Wolne środki są **niezależne od filtrów** Zajęć i okresu — nie mają ani jednego, ani drugiego.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| payerContactId | query | any | Optional |
| courseId | query | any | Optional |
| periodFrom | query | any | Optional |
| periodTo | query | any | Optional |
| onlyOverdue | query | any | Optional |

### Responses

**200** – Salda w zakresie filtrów

Content-Type: `application/json`

```json
{
  "items": [
    {
      "payer_contact_id": "00000000-0000-4000-8000-000000000000",
      "state": "settled",
      "to_pay_minor": "string",
      "overdue_minor": "string",
      "free_funds_minor": "string",
      "currency": "string",
      "charge_count": 1
    }
  ],
  "total": 1,
  "today": "string"
}
```

**400** – Niepoprawne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_billing/balances" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/hoopi_billing/billing-plans`

Delete plan rozliczeniowy

Usuwa Plan miękko. Plan z wystawionymi Należnościami jest zablokowany i zwraca 409.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Plan rozliczeniowy deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_billing/billing-plans" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_billing/billing-plans`

List plany rozliczeniowe

Zwraca stronicowaną listę: plany rozliczeniowe. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| id | query | any | Optional |
| rootPlanId | query | any | Optional |
| activeOnly | query | any | Optional |
| billingUnit | query | any | Optional |
| sortField | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated plany rozliczeniowe

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "billing_unit": "string",
      "amount_minor": null,
      "valid_from": null,
      "supersedes_id": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_billing/billing-plans?page=1&pageSize=50&sortDir=asc&sortField=name" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_billing/billing-plans`

Create plan rozliczeniowy

Tworzy wiersz Planu. Podanie `supersedesId` zakłada nową wersję istniejącej rodziny — kwota i reguły nigdy nie zmieniają się na wierszu już istniejącym.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "billingUnit": "month",
  "paymentTiming": "advance",
  "absenceRule": "forfeit",
  "organizerCancellationRule": "credit",
  "amountMinor": "string",
  "validFrom": "string"
}
```

### Responses

**201** – Plan rozliczeniowy created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/billing-plans" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"billingUnit\": \"month\",
  \"paymentTiming\": \"advance\",
  \"absenceRule\": \"forfeit\",
  \"organizerCancellationRule\": \"credit\",
  \"amountMinor\": \"string\",
  \"validFrom\": \"string\"
}"
```

## PUT `/hoopi_billing/billing-plans`

Update plan rozliczeniowy

Zmienia **wyłącznie nazwę** Planu. Każde pole wyliczeniowe kończy się 400 — zmiana ceny albo reguły wymaga nowej wersji.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Plan rozliczeniowy updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_billing/billing-plans" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\"
}"
```

## GET `/hoopi_billing/charges`

List należności

Zwraca stronicowaną listę: należności. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| id | query | any | Optional |
| seasonId | query | any | Optional |
| courseId | query | any | Optional |
| enrollmentId | query | any | Optional |
| payerContactId | query | any | Optional |
| participantId | query | any | Optional |
| paymentReference | query | any | Optional |
| generationRunId | query | any | Optional |
| status | query | any | Optional |
| dueDateFrom | query | any | Optional |
| dueDateTo | query | any | Optional |
| source | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated należności

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "enrollment_id": "00000000-0000-4000-8000-000000000000",
      "course_id": "00000000-0000-4000-8000-000000000000",
      "participant_id": "00000000-0000-4000-8000-000000000000",
      "payer_contact_id": "00000000-0000-4000-8000-000000000000",
      "billing_plan_id": "00000000-0000-4000-8000-000000000000",
      "period_from": null,
      "period_to": null,
      "amount_minor": null,
      "due_date": null,
      "status": "string",
      "payment_reference": "string",
      "generation_run_id": null,
      "discount_minor": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_billing/charges?page=1&pageSize=50&sortDir=desc&sortField=due_date" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_billing/charges`

Create należność

Wystawia Należność ręcznie (O-5). Kwota jest sumą wpisanych Pozycji — bez proporcji i bez zaokrąglania; okres i termin płatności wpisuje organizator. Okres pokrywający się z inną nieanulowaną Należnością tego Zapisu kończy się 409.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "enrollmentId": "00000000-0000-4000-8000-000000000000",
  "payerContactId": "00000000-0000-4000-8000-000000000000",
  "billingPlanId": "00000000-0000-4000-8000-000000000000",
  "periodFrom": "string",
  "periodTo": "string",
  "dueDate": "string",
  "items": [
    {
      "description": "string",
      "quantity": 1,
      "unitAmountMinor": "string"
    }
  ]
}
```

### Responses

**201** – Należność created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/charges" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"enrollmentId\": \"00000000-0000-4000-8000-000000000000\",
  \"payerContactId\": \"00000000-0000-4000-8000-000000000000\",
  \"billingPlanId\": \"00000000-0000-4000-8000-000000000000\",
  \"periodFrom\": \"string\",
  \"periodTo\": \"string\",
  \"dueDate\": \"string\",
  \"items\": [
    {
      \"description\": \"string\",
      \"quantity\": 1,
      \"unitAmountMinor\": \"string\"
    }
  ]
}"
```

## GET `/hoopi_billing/charges/{id}`

Zwraca Należność razem z Pozycjami

Pozycje i powody Korekt przychodzą rozszyfrowane; kolejność jak przy wystawieniu. Pola addytywne specu G: `adjustments`, `reduction_minor`, `amount_due_minor`, `covered_minor`, `missing_minor`, `cancellable`.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Należności |

### Responses

**200** – Należność

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "enrollment_id": "00000000-0000-4000-8000-000000000000",
  "course_id": "00000000-0000-4000-8000-000000000000",
  "participant_id": "00000000-0000-4000-8000-000000000000",
  "payer_contact_id": "00000000-0000-4000-8000-000000000000",
  "billing_plan_id": "00000000-0000-4000-8000-000000000000",
  "period_from": null,
  "period_to": null,
  "amount_minor": null,
  "currency": null,
  "vat_rate": null,
  "is_gross": null,
  "due_date": null,
  "status": "string",
  "payment_reference": "string",
  "generation_run_id": null,
  "cancelled_at": null,
  "cancelled_reason": null,
  "cancellation_source": null,
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "description": null,
      "quantity": 1,
      "unit_amount_minor": null,
      "vat_rate": null,
      "session_id": null
    }
  ],
  "has_allocations": true,
  "adjustments": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "type": "string",
      "amount_minor": "string",
      "currency": null,
      "reason": null,
      "rule_name": null,
      "discount_rule_id": null,
      "discount_eligibility_id": null,
      "created_at": null,
      "created_by_user_id": null,
      "created_by_label": null,
      "generation_run_id": null,
      "reduces_amount": true
    }
  ],
  "reduction_minor": "string",
  "amount_due_minor": "string",
  "covered_minor": "string",
  "missing_minor": "string",
  "cancellable": true
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Należność nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_billing/charges/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_billing/charges/{id}/cancel`

Anuluje Należność z podanym powodem

Powód jest wymagany i trafia do dziennika działań razem z autorem i datą.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Należności |

### Request Body

Content-Type: `application/json`

```json
{
  "reason": "string"
}
```

### Responses

**200** – Należność anulowana

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "status": "string"
}
```

**400** – Brak powodu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Należność nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Do Należności jest zaksięgowana wpłata — anulowanie osierociłoby pieniądze (`hoopi_billing.charge_has_allocations`). Droga wyjścia to zwrot przez Korektę.

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/charges/00000000-0000-4000-8000-000000000000/cancel" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"reason\": \"string\"
}"
```

## POST `/hoopi_billing/charges/generate`

Nalicza Należności za wskazany miesiąc

Jeden przebieg obejmuje jeden miesiąc kalendarzowy jednego Sezonu. `dryRun: true` zwraca identyczny raport bez zapisu i bez wiersza audytu. Powtórzony przebieg pomija Zapisy, które mają już Należność za ten okres (`already_charged`), i dogenerowuje nowe. Spec G: przy Należności z przysługującą zniżką przebieg zakłada w tej samej transakcji Korektę `discount` (najwyżej jedną — najkorzystniejszą); `discountNotes` wyjaśnia zniżki, które nie weszły, a `skipped[].reason = discount_failed` oznacza Należność niewystawioną, bo zapis zniżki się nie udał.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "seasonId": "00000000-0000-4000-8000-000000000000",
  "period": "string"
}
```

### Responses

**200** – Raport przebiegu

Content-Type: `application/json`

```json
{
  "generationRunId": null,
  "dryRun": true,
  "period": "string",
  "issued": [
    {
      "enrollmentId": "00000000-0000-4000-8000-000000000000",
      "participantId": "00000000-0000-4000-8000-000000000000",
      "courseId": "00000000-0000-4000-8000-000000000000",
      "chargeId": null,
      "amountMinor": "string",
      "discount": null
    }
  ],
  "skipped": [
    {
      "enrollmentId": "00000000-0000-4000-8000-000000000000",
      "participantId": "00000000-0000-4000-8000-000000000000",
      "courseId": "00000000-0000-4000-8000-000000000000",
      "reason": "string"
    }
  ],
  "totalAmountMinor": "string",
  "totalDiscountMinor": "string",
  "discountNotes": [
    {
      "participantId": "00000000-0000-4000-8000-000000000000",
      "courseId": "00000000-0000-4000-8000-000000000000",
      "kind": "eligibility_expired",
      "ruleName": "string",
      "validUntil": null
    }
  ]
}
```

**400** – Błędne wejście, miesiąc poza Sezonem albo `actor_required` (zniżki bez zalogowanego autora)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Sezon nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/charges/generate" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"seasonId\": \"00000000-0000-4000-8000-000000000000\",
  \"period\": \"string\"
}"
```

## POST `/hoopi_billing/charges/generate-undo`

Anuluje wszystkie Należności jednego przebiegu

Fail-closed: 409, gdy którakolwiek Należność przebiegu jest opłacona, częściowo opłacona albo zwrócona. Ponowne cofnięcie tego samego przebiegu też kończy się 409.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "generationRunId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**200** – Liczba anulowanych Należności

Content-Type: `application/json`

```json
{
  "cancelled": 1
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Przebieg nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Przebieg zawiera rozliczone Należności albo został już cofnięty

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/charges/generate-undo" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"generationRunId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_billing/course-discount-policy`

Czy zniżki obejmują te Zajęcia

Bez zapisanej polityki oddaje wartość domyślną rodzaju Zajęć (`group` — włączone, pozostałe — wyłączone) z `isDefault: true`.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| courseId | query | any | Required |

### Responses

**200** – Polityka

Content-Type: `application/json`

```json
{
  "courseId": "00000000-0000-4000-8000-000000000000",
  "discountsEnabled": true,
  "isDefault": true,
  "courseKind": "string"
}
```

**404** – Zajęcia spoza zakresu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_billing/course-discount-policy?courseId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/hoopi_billing/course-discount-policy`

Włącza albo wyłącza zniżki na Zajęciach

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "courseId": "00000000-0000-4000-8000-000000000000",
  "discountsEnabled": true
}
```

### Responses

**200** – Zapisano

Content-Type: `application/json`

```json
{
  "courseId": "00000000-0000-4000-8000-000000000000",
  "discountsEnabled": true,
  "isDefault": true,
  "courseKind": "string"
}
```

**400** – Błędne dane wejściowe

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Zajęcia spoza zakresu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_billing/course-discount-policy" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"courseId\": \"00000000-0000-4000-8000-000000000000\",
  \"discountsEnabled\": true
}"
```

## DELETE `/hoopi_billing/course-plan-allowlist`

Zdejmuje wpis z listy dozwolonych Kursu

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Zdjęto

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**404** – Nie ma takiego wpisu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_billing/course-plan-allowlist" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_billing/course-plan-allowlist`

Zwraca plany dozwolone dla Kursu

Z `withDefault=true` dokłada domyślny plan Kursu — wynik jest wtedy dokładnie zbiorem, który egzekwuje interceptor przy zakładaniu Zapisu.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| courseId | query | any | Required |
| withDefault | query | any | Optional |

### Responses

**200** – Lista

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "rootPlanId": "00000000-0000-4000-8000-000000000000",
      "planId": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "amountMinor": "string",
      "currency": "string",
      "isDefault": true
    }
  ]
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_billing/course-plan-allowlist?courseId=00000000-0000-4000-8000-000000000000&withDefault=false" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_billing/course-plan-allowlist`

Dodaje rodzinę planu do listy dozwolonych Kursu

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "courseId": "00000000-0000-4000-8000-000000000000",
  "rootPlanId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**201** – Dodano

Content-Type: `application/json`

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

**400** – Plan nie istnieje albo nie jest korzeniem rodziny

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Plan jest już na liście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/course-plan-allowlist" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"courseId\": \"00000000-0000-4000-8000-000000000000\",
  \"rootPlanId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_billing/discount-eligibilities`

List uprawnienia do zniżek

Zwraca stronicowaną listę: uprawnienia do zniżek. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| id | query | any | Optional |
| participantId | query | any | Required |
| includeRevoked | query | any | Required |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated uprawnienia do zniżek

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "participant_id": "00000000-0000-4000-8000-000000000000",
      "root_rule_id": "00000000-0000-4000-8000-000000000000",
      "kind": "string",
      "source": "string",
      "declared_at": null,
      "declared_by_user_id": null,
      "enrollment_id": null,
      "valid_until": null,
      "override_reason": null,
      "revoked_at": null,
      "revoked_reason": null,
      "rule_name": null,
      "rule_kind": null,
      "declared_by_label": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_billing/discount-eligibilities?page=1&pageSize=50&sortDir=desc&sortField=declared_at&participantId=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_billing/discount-eligibilities`

Create uprawnienie do zniżki

Wpisuje deklarację uprawnienia do reguły **deklarowanej** (fakt i data ważności — bez numeru karty i bez skanu). Reguła rodzeństwa → 400 `rule_kind_mismatch` (użyj `sibling-override`); drugie żywe uprawnienie do tej samej reguły → 409 `eligibility_exists`.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "participantId": "00000000-0000-4000-8000-000000000000",
  "ruleId": "00000000-0000-4000-8000-000000000000",
  "validUntil": null,
  "enrollmentId": null
}
```

### Responses

**201** – Uprawnienie do zniżki created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/discount-eligibilities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"participantId\": \"00000000-0000-4000-8000-000000000000\",
  \"ruleId\": \"00000000-0000-4000-8000-000000000000\",
  \"validUntil\": null,
  \"enrollmentId\": null
}"
```

## PUT `/hoopi_billing/discount-eligibilities`

Update uprawnienie do zniżki

Zmienia wyłącznie datę ważności. Uprawnienie cofnięte → 409; uznanie rodzeństwa → 400. Wystawionych Należności nie przelicza.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "validUntil": null
}
```

### Responses

**200** – Uprawnienie do zniżki updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_billing/discount-eligibilities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"validUntil\": null
}"
```

## POST `/hoopi_billing/discount-eligibilities/{id}/revoke`

Cofa uprawnienie z powodem — od następnego przebiegu, nigdy wstecz

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Uprawnienia |

### Request Body

Content-Type: `application/json`

```json
{
  "reason": "string"
}
```

### Responses

**200** – Cofnięto

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Brak powodu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie ma takiego uprawnienia w tej organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Uprawnienie jest już cofnięte

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/discount-eligibilities/00000000-0000-4000-8000-000000000000/revoke" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"reason\": \"string\"
}"
```

## POST `/hoopi_billing/discount-eligibilities/sibling-override`

Ręcznie uznaje Uczestnika za rodzeństwo dla reguły rodzeństwa — z powodem

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "participantId": "00000000-0000-4000-8000-000000000000",
  "ruleId": "00000000-0000-4000-8000-000000000000",
  "reason": "string"
}
```

### Responses

**201** – Uznanie zapisane

Content-Type: `application/json`

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

**400** – Reguła nie jest regułą rodzeństwa (`rule_kind_mismatch`), reguła zarchiwizowana albo brak powodu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy `hoopi_billing.discounts.grant`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Uczestnik albo reguła nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Żywe uprawnienie do tej reguły już jest (`eligibility_exists`)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/discount-eligibilities/sibling-override" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"participantId\": \"00000000-0000-4000-8000-000000000000\",
  \"ruleId\": \"00000000-0000-4000-8000-000000000000\",
  \"reason\": \"string\"
}"
```

## GET `/hoopi_billing/discount-rules`

List reguły zniżek

Zwraca stronicowaną listę: reguły zniżek. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| id | query | any | Optional |
| rootRuleId | query | any | Optional |
| kind | query | any | Optional |
| archived | query | any | Required |
| activeOnly | query | any | Required |
| includeArchived | query | any | Required |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated reguły zniżek

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "kind": "string",
      "value_type": "string",
      "percent_bp": null,
      "amount_minor": null,
      "sibling_position_from": null,
      "public_description": null,
      "valid_from": null,
      "supersedes_id": null,
      "root_rule_id": "00000000-0000-4000-8000-000000000000",
      "archived_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_billing/discount-rules?page=1&pageSize=50&sortDir=asc&sortField=sort_order" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_billing/discount-rules`

Create reguła zniżki

Tworzy wiersz reguły. `supersedesId` zakłada nową wersję istniejącej rodziny — wartość nigdy nie zmienia się na wierszu istniejącym. 409: `supersedes_not_leaf`, `kind_mismatch`, `rule_archived`, `rule_version_exists`.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "kind": "sibling",
  "valueType": "percent",
  "publicDescription": null,
  "validFrom": "string"
}
```

### Responses

**201** – Reguła zniżki created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/discount-rules" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"kind\": \"sibling\",
  \"valueType\": \"percent\",
  \"publicDescription\": null,
  \"validFrom\": \"string\"
}"
```

## PUT `/hoopi_billing/discount-rules`

Update reguła zniżki

Zmienia wyłącznie nazwę, opis dla rodzica i kolejność. Pole wartości kończy się 400 `rule_row_is_immutable` — zmiana wymaga nowej wersji.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "publicDescription": null
}
```

### Responses

**200** – Reguła zniżki updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_billing/discount-rules" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"publicDescription\": null
}"
```

## POST `/hoopi_billing/discount-rules/{id}/archive`

Archiwizuje rodzinę reguły — przebieg przestaje ją stosować, wystawione zniżki zostają

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Zarchiwizowano

Content-Type: `application/json`

```json
{
  "ok": true,
  "rootRuleId": "00000000-0000-4000-8000-000000000000"
}
```

**404** – Nie ma takiej reguły w tej organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Reguła jest już zarchiwizowana

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/discount-rules/:id/archive" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/hoopi_billing/payments`

Zwraca stronicowaną listę Wpłat

Zakres najemcy i organizacji pochodzi z kontekstu żądania. Filtr `chargeId` zawęża listę do Wpłat pokrywających wskazaną Należność. Notatka jest szyfrowana i nie wchodzi na listę.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| id | query | any | Optional |
| payerContactId | query | any | Optional |
| method | query | any | Optional |
| source | query | any | Optional |
| status | query | any | Optional |
| chargeId | query | any | Optional |
| receivedFrom | query | any | Optional |
| receivedTo | query | any | Optional |
| reference | query | any | Optional |

### Responses

**200** – Lista Wpłat

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "payer_contact_id": "00000000-0000-4000-8000-000000000000",
      "amount_minor": null,
      "captured_amount_minor": null,
      "refunded_amount_minor": null,
      "method": "string",
      "received_on": null,
      "reference": null,
      "source": null,
      "status": "string",
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_billing/payments?page=1&pageSize=50&sortDir=desc&sortField=received_on" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_billing/payments`

Księguje Wpłatę albo pokazuje propozycję rozdysponowania

Z `dryRun` zwraca propozycję i kandydatki bez zapisu (200). Bez `dryRun` zapisuje Wpłatę razem z potwierdzoną propozycją (201); powtórzony `idempotencyToken` oddaje istniejącą Wpłatę (200), bo podwójne kliknięcie ma dać jedną Wpłatę. Kwota alokacji bywa przycięta do brakującej części Należności — wtedy `appliedMinor` mówi, ile faktycznie weszło, a reszta zostaje wolnymi środkami.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "payerContactId": "00000000-0000-4000-8000-000000000000",
  "amountMinor": "string",
  "currency": "string",
  "method": "transfer",
  "receivedOn": "string"
}
```

### Responses

**200** – Propozycja albo duplikat

Content-Type: `application/json`

```json
{
  "dryRun": true,
  "duplicate": true,
  "paymentId": null,
  "payerContactId": "00000000-0000-4000-8000-000000000000",
  "amountMinor": "string",
  "currency": "string",
  "allocations": [
    {
      "chargeId": "00000000-0000-4000-8000-000000000000",
      "requestedMinor": "string",
      "appliedMinor": "string",
      "chargeStatus": "string",
      "clampedReason": null
    }
  ],
  "unallocatedMinor": "string",
  "proposal": null,
  "candidates": null
}
```

**201** – Wpłata zaksięgowana

Content-Type: `application/json`

```json
{
  "dryRun": true,
  "duplicate": true,
  "paymentId": null,
  "payerContactId": "00000000-0000-4000-8000-000000000000",
  "amountMinor": "string",
  "currency": "string",
  "allocations": [
    {
      "chargeId": "00000000-0000-4000-8000-000000000000",
      "requestedMinor": "string",
      "appliedMinor": "string",
      "chargeStatus": "string",
      "clampedReason": null
    }
  ],
  "unallocatedMinor": "string",
  "proposal": null,
  "candidates": null
}
```

**400** – Niepoprawne wejście albo nieznany płatnik

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Należność z propozycji nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Alokacja zabroniona: Należność anulowana, zwrócona, innego płatnika albo w innej walucie (§4.3)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/payments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"payerContactId\": \"00000000-0000-4000-8000-000000000000\",
  \"amountMinor\": \"string\",
  \"currency\": \"string\",
  \"method\": \"transfer\",
  \"receivedOn\": \"string\"
}"
```

## GET `/hoopi_billing/payments/{id}`

Zwraca Wpłatę razem z Alokacjami i wolnymi środkami

Notatka i powód cofnięcia przychodzą rozszyfrowane. `unallocated_minor` jest liczone przy odczycie — kolumny „nierozdysponowane" nie ma.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Wpłaty |

### Responses

**200** – Wpłata

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "payer_contact_id": "00000000-0000-4000-8000-000000000000",
  "amount_minor": null,
  "captured_amount_minor": null,
  "refunded_amount_minor": null,
  "currency": null,
  "method": "string",
  "received_on": null,
  "reference": null,
  "source": null,
  "note": null,
  "status": "string",
  "reversed_at": null,
  "reversed_reason": null,
  "allocations": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "charge_id": "00000000-0000-4000-8000-000000000000",
      "amount_minor": null,
      "charge_payment_reference": null,
      "charge_status": null,
      "charge_period_from": null,
      "charge_period_to": null
    }
  ],
  "allocated_minor": "string",
  "unallocated_minor": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Wpłata nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_billing/payments/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_billing/payments/{id}/allocate`

Dopisuje alokacje z wolnych środków istniejącej Wpłaty

Kwota bywa przycięta do brakującej części Należności albo do wolnych środków — `appliedMinor` mówi, ile faktycznie weszło. Statusy dotkniętych Należności są przeliczane w tej samej transakcji.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Wpłaty |

### Request Body

Content-Type: `application/json`

```json
{
  "allocations": [
    {
      "chargeId": "00000000-0000-4000-8000-000000000000",
      "amountMinor": "string"
    }
  ]
}
```

### Responses

**200** – Alokacje zapisane

Content-Type: `application/json`

```json
{
  "dryRun": true,
  "duplicate": true,
  "paymentId": null,
  "payerContactId": "00000000-0000-4000-8000-000000000000",
  "amountMinor": "string",
  "currency": "string",
  "allocations": [
    {
      "chargeId": "00000000-0000-4000-8000-000000000000",
      "requestedMinor": "string",
      "appliedMinor": "string",
      "chargeStatus": "string",
      "clampedReason": null
    }
  ],
  "unallocatedMinor": "string",
  "proposal": null,
  "candidates": null
}
```

**400** – Niepoprawne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Wpłata albo Należność nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Wpłata cofnięta, bez wolnych środków, albo alokacja zabroniona przez §4.3 (Należność anulowana, zwrócona, innego płatnika, inna waluta)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/payments/00000000-0000-4000-8000-000000000000/allocate" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"allocations\": [
    {
      \"chargeId\": \"00000000-0000-4000-8000-000000000000\",
      \"amountMinor\": \"string\"
    }
  ]
}"
```

## POST `/hoopi_billing/payments/{id}/reverse`

Nadaje Wpłacie status `reversed`, kasuje alokacje i przelicza Należności

Jedyne przejście Wpłaty i **nieodwracalne**: cofnięcie cofniętej Wpłaty wraca 409. Pomyłkę prostuje się nową Wpłatą, nie cofnięciem cofnięcia.

**Tags:** Rozliczenia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Wpłaty |

### Request Body

Content-Type: `application/json`

```json
{
  "reason": "string"
}
```

### Responses

**200** – Wpłata cofnięta

Content-Type: `application/json`

```json
{
  "paymentId": "00000000-0000-4000-8000-000000000000",
  "reversedAt": null,
  "reason": "string",
  "recalculatedChargeIds": [
    "00000000-0000-4000-8000-000000000000"
  ],
  "tenantId": null,
  "organizationId": null
}
```

**400** – Niepoprawne wejście albo brak powodu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Wpłata nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Wpłata jest już cofnięta albo ma zarejestrowany zwrot — wtedy najpierw odwróć skutek księgowy (§4.3)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/payments/00000000-0000-4000-8000-000000000000/reverse" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"reason\": \"string\"
}"
```

## POST `/hoopi_billing/payments/allocate-bulk`

Rozdysponowuje wolne środki wielu płatników po przebiegu naliczania

Z `dryRun: true` oddaje samą listę do pokazania przed zapisem — propozycja nigdy nie jest stosowana w tle (Q3). Płatnicy idą po kolei, każdy we własnej transakcji: porażka jednego nie cofa rozliczeń reszty.

**Tags:** Rozliczenia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Lista propozycji albo wynik zapisu

Content-Type: `application/json`

```json
{
  "dryRun": true,
  "chargeCount": 1,
  "payerCount": 1,
  "totalMinor": "string",
  "lines": [
    {
      "payerContactId": "00000000-0000-4000-8000-000000000000",
      "paymentId": "00000000-0000-4000-8000-000000000000",
      "chargeId": "00000000-0000-4000-8000-000000000000",
      "amountMinor": "string"
    }
  ],
  "tenantId": null,
  "organizationId": null
}
```

**400** – Niepoprawne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_billing/payments/allocate-bulk" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## GET `/hoopi_classes/attendance-tickets`

Lista Udziałów — wymaga sessionId albo participantId

Udziały `regular` są projekcją utrzymywaną przez `sync-attendance-tickets`, nie mają własnej trasy zapisu. Każdy wiersz niesie `attendance_record`, jeśli Obecność już istnieje.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| sessionId | query | any | Optional |
| participantId | query | any | Optional |
| enrollmentId | query | any | Optional |
| kind | query | any | Optional |
| status | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |

### Responses

**200** – Lista Udziałów

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "participant_id": "00000000-0000-4000-8000-000000000000",
      "session_id": "00000000-0000-4000-8000-000000000000",
      "enrollment_id": null,
      "kind": "string",
      "source_session_id": null,
      "expires_on": null,
      "status": "string",
      "requested_at": null,
      "decided_at": null,
      "decided_by_user_id": null,
      "updated_at": "string",
      "attendance_record": null
    }
  ],
  "totalCount": 1
}
```

**400** – Brak sessionId i participantId, albo błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/attendance-tickets?page=1&pageSize=50&sortDir=asc&sortField=created_at" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/attendance-tickets`

Dodaje Udział spoza projekcji: odrobienie, próbne albo jednorazowy

`enrollment` jest zawsze puste — wejście nie niesie `enrollmentId`. `sourceSessionId` wymagane dla `make_up`. `capacity_override` wypełniony liczy się razem z `regular`.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "participantId": "00000000-0000-4000-8000-000000000000",
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "kind": "make_up",
  "sourceSessionId": null,
  "expiresOn": null
}
```

### Responses

**201** – Udział utworzony

Content-Type: `application/json`

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

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Uczestnik albo Termin nie istnieją w tym zakresie — także Termin cudzych Zajęć dla prowadzącego bez view_all

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Duplikat pary (uczestnik, Termin), Termin nieotwarty, albo przekroczony wypełniony `capacity_override`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**422** – Termin docelowy jest już po dacie ważności odrobienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/attendance-tickets" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"participantId\": \"00000000-0000-4000-8000-000000000000\",
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"kind\": \"make_up\",
  \"sourceSessionId\": null,
  \"expiresOn\": null
}"
```

## POST `/hoopi_classes/attendance-tickets/{id}/revert-make-up`

Cofnięcie made_up → to_make_up na pierwotnym Udziale (Q-112)

Korekta błędnie odhaczonej obecności odrabiającego. Nie dotyka statusu Terminu ani frekwencji reszty grupy — osobna droga zamiast wyjątku od `held → cancelled`.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Udziału „make_up” |

### Request Body

Content-Type: `application/json`

```json
{
  "updatedAt": "string"
}
```

### Responses

**200** – Cofnięte

Content-Type: `application/json`

```json
{
  "ticket_id": "00000000-0000-4000-8000-000000000000",
  "original_attendance_record_id": "00000000-0000-4000-8000-000000000000"
}
```

**400** – Udział nie jest „make_up” albo nie ma pierwotnego Terminu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak uprawnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Udział nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Konflikt wersji, brak pierwotnego Udziału, albo pierwotna Obecność nie jest „made_up”

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/attendance-tickets/00000000-0000-4000-8000-000000000000/revert-make-up" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"updatedAt\": \"string\"
}"
```

## GET `/hoopi_classes/calendar`

Terminy i Dni wolne w zakresie dat, z nazwami

Odczyt bez paginacji: zwraca wszystkie Terminy i Dni wolne zakresu, wzbogacone o nazwy Kursu, Sekcji, Sali i prowadzącego, oraz liczniki w `meta`. Zakres domknięty z obu stron, najwyżej 62 dni. Filtr Sekcji obejmuje jej poddrzewo; Dni wolne całej placówki widać zawsze. Powód odwołania Terminu nie jest zwracany — pozostaje na widoku Kursu.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| from | query | any | Required |
| to | query | any | Required |
| sectionId | query | any | Optional |
| courseId | query | any | Optional |
| staffMemberId | query | any | Optional |
| resourceId | query | any | Optional |

### Responses

**200** – Terminy, Dni wolne i liczniki

Content-Type: `application/json`

```json
{
  "sessions": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "courseId": "00000000-0000-4000-8000-000000000000",
      "courseName": "string",
      "courseKind": "group",
      "sectionId": null,
      "sectionName": null,
      "date": "string",
      "startsAt": "string",
      "endsAt": "string",
      "status": "planned",
      "resourceId": null,
      "resourceName": null,
      "venueName": null,
      "staffMemberId": null,
      "staffMemberName": null,
      "isSubstitute": true,
      "scheduleLineId": null,
      "scheduleLineUpdatedAt": null,
      "updatedAt": "string"
    }
  ],
  "closures": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "startsOn": "string",
      "endsOn": "string",
      "sectionId": null,
      "sectionName": null,
      "blocksSessions": true,
      "source": "import",
      "updatedAt": "string"
    }
  ],
  "meta": {
    "sessionCount": 1,
    "closureCount": 1,
    "courseCount": 1
  }
}
```

**400** – Brak lub zły format `from`/`to`, zakres dłuższy niż 62 dni, Sekcja spoza zakresu albo brak kontekstu organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak uprawnienia `hoopi_classes.view`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/calendar?from=string&to=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/hoopi_classes/closure-days`

Delete dzień wolny

Usuwa Dzień wolny miękko. Terminy raz pominięte przez generator nie wracają same — dosypie je dopiero regeneracja.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Dzień wolny deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_classes/closure-days" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/closure-days`

List dni wolne

Zwraca stronicowaną listę: dni wolne. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| scopeSectionId | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated dni wolne

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "starts_on": null,
      "ends_on": null,
      "scope_section_id": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/closure-days?page=1&pageSize=50&sortDir=asc&sortField=starts_on" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/closure-days`

Create dzień wolny

Tworzy Dzień wolny. Puste `scopeSectionId` obejmuje całego organizatora; wypełnione — wskazany Kurs i jego poddrzewo.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "startsOn": "string",
  "endsOn": "string",
  "scopeSectionId": null
}
```

### Responses

**201** – Dzień wolny created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/closure-days" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"startsOn\": \"string\",
  \"endsOn\": \"string\",
  \"scopeSectionId\": null
}"
```

## PUT `/hoopi_classes/closure-days`

Update dzień wolny

Zmienia Dzień wolny. `updatedAt` włącza blokadę optymistyczną i daje 409 przy konflikcie.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "scopeSectionId": null
}
```

### Responses

**200** – Dzień wolny updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/closure-days" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"scopeSectionId\": null
}"
```

## DELETE `/hoopi_classes/courses`

Delete zajęcia

Usuwa Zajęcia miękko wraz z Terminami bez Obecności. Zapis zajmujący miejsce albo Termin z Obecnością zwraca 409 — wtedy właściwym ruchem jest `lifecycleStatus: "archived"`.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Zajęcia deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_classes/courses" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/courses`

List zajęcia

Zwraca stronicowaną listę: zajęcia. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |
| sectionId | query | any | Optional |
| seasonId | query | any | Optional |
| staffMemberId | query | any | Optional |
| kind | query | any | Optional |
| lifecycleStatus | query | any | Optional |
| enrollmentOpen | query | any | Optional |
| isPublic | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |

### Responses

**200** – Paginated zajęcia

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "kind": null,
      "section_id": null,
      "season_id": null,
      "staff_member_id": null,
      "resource_id": null,
      "capacity": null,
      "lifecycle_status": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/courses?page=1&pageSize=50&sortDir=asc&sortField=name" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/courses`

Create zajęcia

Tworzy Zajęcia. `kind` i `lifecycleStatus` są wymagane. `kind` jest **niezmienny** po utworzeniu, a w Fazie 1 przyjmowana jest wyłącznie wartość `group` — pozostałe trzy odmawiają kodem `hoopi_classes.kind_not_available_yet`. `lifecycleStatus` jest niezależne od `enrollmentOpen` oraz `isPublic`. Kurs i Sezon muszą należeć do tej organizacji.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "sectionId": "00000000-0000-4000-8000-000000000000",
  "seasonId": "00000000-0000-4000-8000-000000000000",
  "staffMemberId": null,
  "resourceId": null,
  "capacity": 1,
  "minCapacity": null,
  "ageFrom": null,
  "ageTo": null,
  "kind": "group",
  "lifecycleStatus": "draft",
  "description": null,
  "defaultBillingPlanId": null,
  "k07Category": null
}
```

### Responses

**201** – Zajęcia created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/courses" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"sectionId\": \"00000000-0000-4000-8000-000000000000\",
  \"seasonId\": \"00000000-0000-4000-8000-000000000000\",
  \"staffMemberId\": null,
  \"resourceId\": null,
  \"capacity\": 1,
  \"minCapacity\": null,
  \"ageFrom\": null,
  \"ageTo\": null,
  \"kind\": \"group\",
  \"lifecycleStatus\": \"draft\",
  \"description\": null,
  \"defaultBillingPlanId\": null,
  \"k07Category\": null
}"
```

## PUT `/hoopi_classes/courses`

Update zajęcia

Zmienia Zajęcia. `updatedAt` włącza blokadę optymistyczną i daje 409 przy konflikcie. `kind` wolno przysłać, ale wyłącznie z wartością identyczną z zapisaną — inna kończy się 400 `hoopi_classes.kind_immutable`.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "staffMemberId": null,
  "resourceId": null,
  "minCapacity": null,
  "ageFrom": null,
  "ageTo": null,
  "description": null,
  "defaultBillingPlanId": null,
  "k07Category": null
}
```

### Responses

**200** – Zajęcia updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/courses" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"staffMemberId\": null,
  \"resourceId\": null,
  \"minCapacity\": null,
  \"ageFrom\": null,
  \"ageTo\": null,
  \"description\": null,
  \"defaultBillingPlanId\": null,
  \"k07Category\": null
}"
```

## POST `/hoopi_classes/courses/{id}/copy-to-season`

Kopiuje Zajęcia i ich linie Harmonogramu do nowego Sezonu

Tworzy nowe Zajęcia (`previous_course_id` wskazuje źródło), kopiuje linie grupowe Harmonogramu bez dat obowiązywania. Nie kopiuje Terminów, Zapisów ani Obecności — źródło zostaje nietknięte. Dotyczy wyłącznie Zajęć `kind: "group"`; pozostałe typy odmawiają kodem `hoopi_classes.copy_not_allowed_for_kind`, bo kopia bez Uczestnika albo bez Terminu łamałaby własny niezmiennik typu.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator źródłowych Zajęć |

### Request Body

Content-Type: `application/json`

```json
{
  "targetSeasonId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**201** – Nowe Zajęcia utworzone

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "previousCourseId": "00000000-0000-4000-8000-000000000000"
}
```

**400** – Błędne wejście albo docelowy Sezon jest taki sam jak obecny

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Zajęcia nie istnieją w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/courses/00000000-0000-4000-8000-000000000000/copy-to-season" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"targetSeasonId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/hoopi_classes/courses/{id}/finish`

Kończy Zajęcia: zdejmuje Terminy po dacie, datuje otwarte Zapisy, zamyka linie Harmonogramu

Zdejmuje żywe Terminy po `endsOn` (niezależnie od statusu) wraz z ich Udziałami, ustawia `ends_on` na otwartych Zapisach bez zmiany statusu, przycina `validTo` linii Harmonogramu, przełącza Zajęcia na `finished`. `dryRun` liczy skutki i nie zapisuje nic.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Zajęć |

### Request Body

Content-Type: `application/json`

```json
{
  "endsOn": "string",
  "updatedAt": "string",
  "dryRun": false
}
```

### Responses

**200** – Raport skutków

Content-Type: `application/json`

```json
{
  "removedSessions": 1,
  "removedSessionsWithAttendance": 1,
  "removedTickets": 1,
  "removedForeignTickets": 1,
  "datedEnrollments": 1,
  "closedScheduleLines": 1
}
```

**400** – Data zakończenia w przeszłości albo błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Zajęcia nie istnieją w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Zajęcia są już zakończone albo zarchiwizowane, albo wersja jest nieaktualna

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/courses/00000000-0000-4000-8000-000000000000/finish" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"endsOn\": \"string\",
  \"updatedAt\": \"string\",
  \"dryRun\": false
}"
```

## POST `/hoopi_classes/courses/{id}/generate-sessions`

Generuje Terminy Kursu z linii Harmonogramu

Idempotentne po trójce (Kurs, linia, data). Zakresem domyślnym jest Sezon Kursu; `from` i `to` mogą go wyłącznie zawęzić. Termin z odhaczoną Obecnością jest nietykalny, Termin z cudzym odrobieniem aktualizowany w miejscu, pozostałe przyszłe `planned` odtwarzane. `dryRun` liczy wszystko i nie zapisuje nic.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Kursu |

### Request Body

Content-Type: `application/json`

```json
{
  "dryRun": false
}
```

### Responses

**200** – Raport przebiegu

Content-Type: `application/json`

```json
{
  "generationRunId": null,
  "created": 1,
  "updatedKept": 1,
  "skippedWithAttendance": 1,
  "skippedClosure": 1,
  "skippedExisting": 1,
  "dstWarnings": [
    {
      "scheduleLineId": "00000000-0000-4000-8000-000000000000",
      "date": "string",
      "startsAt": "string"
    }
  ],
  "participantsToNotify": [
    "00000000-0000-4000-8000-000000000000"
  ],
  "dryRun": true
}
```

**400** – Błędne wejście albo brak kontekstu organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Kurs nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Kurs jest zarchiwizowany

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/courses/00000000-0000-4000-8000-000000000000/generate-sessions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"dryRun\": false
}"
```

## POST `/hoopi_classes/courses/{id}/generate-sessions/{runId}/undo`

Cofa przebieg generatora Terminów

Soft-deletuje Terminy utworzone w podanym przebiegu. Termin z Obecnością albo z cudzym odrobieniem zostaje i jest policzony w `skippedWithAttendance`.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Kursu |
| runId | path | any | Required. Identyfikator przebiegu generatora |

### Responses

**200** – Raport cofnięcia

Content-Type: `application/json`

```json
{
  "generationRunId": "00000000-0000-4000-8000-000000000000",
  "removed": 1,
  "skippedWithAttendance": 1
}
```

**400** – Błędne wejście albo brak kontekstu organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Kurs nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/courses/00000000-0000-4000-8000-000000000000/generate-sessions/00000000-0000-4000-8000-000000000000/undo" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/courses/{id}/reassign-staff`

Przepisuje obsadę na przyszłe Terminy będące kopią poprzedniej

Obejmuje **wyłącznie** przyszłe Terminy `planned` bez Obecności, których `staff_member_id` równa się poprzedniej obsadzie Zajęć. Zastępstwa, Terminy z przeszłości i odhaczone zostają nietknięte i wracają w raporcie jako pominięte. `dryRun` oddaje raport bez zapisu. Komenda jest **cofalna** — cofnięcie przywraca poprzednią obsadę na tych samych Terminach, pomijając te, które ktoś w międzyczasie zmienił ręcznie.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Zajęć |

### Request Body

Content-Type: `application/json`

```json
{
  "previousStaffMemberId": null,
  "newStaffMemberId": null
}
```

### Responses

**200** – Raport przepisania

Content-Type: `application/json`

```json
{}
```

**400** – Poprzednia i nowa obsada są takie same

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono Zajęć

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/courses/00000000-0000-4000-8000-000000000000/reassign-staff" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"previousStaffMemberId\": null,
  \"newStaffMemberId\": null
}"
```

## POST `/hoopi_classes/courses/create-individual`

Zajęcia indywidualne w jednej transakcji

Zakłada Zajęcia `kind: "individual"` z `capacity: 1`, Zapis wskazanego Uczestnika w statusie `active` oraz linię Harmonogramu wskazującą ten Zapis — wszystko w jednej transakcji. `kind` i `capacity` nie są przyjmowane w ciele: trasa jest wyborem typu, a zajęcia dla jednej osoby mają dokładnie jedno miejsce. Cofnięcie z dziennika działań zdejmuje wyłącznie te trzy rekordy i odmawia, gdy którykolwiek Termin ma odhaczoną Obecność (`hoopi_classes.course_has_attendance`), gdy doszedł obcy Zapis (`hoopi_classes.foreign_enrollment`) albo gdy własny Zapis zdążył zmienić status (`hoopi_classes.enrollment_has_history`).

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "course": {
    "name": "string",
    "sectionId": "00000000-0000-4000-8000-000000000000",
    "seasonId": "00000000-0000-4000-8000-000000000000",
    "staffMemberId": null,
    "resourceId": null,
    "minCapacity": null,
    "ageFrom": null,
    "ageTo": null,
    "lifecycleStatus": "draft",
    "description": null,
    "defaultBillingPlanId": null,
    "k07Category": null
  },
  "participantId": "00000000-0000-4000-8000-000000000000",
  "scheduleLine": {
    "weekday": 1,
    "startsAt": "string",
    "endsAt": "string",
    "resourceId": null,
    "staffMemberId": null,
    "validFrom": null,
    "validTo": null
  },
  "source": "panel",
  "startsOn": null,
  "billingPlanId": null,
  "payerContactId": null
}
```

### Responses

**201** – Zajęcia, Zapis i linia Harmonogramu utworzone

Content-Type: `application/json`

```json
{
  "courseId": "00000000-0000-4000-8000-000000000000",
  "enrollmentId": "00000000-0000-4000-8000-000000000000",
  "scheduleLineId": "00000000-0000-4000-8000-000000000000"
}
```

**400** – Błędne wejście albo Kurs, Sezon lub Uczestnik spoza tej organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy `hoopi_classes.manage`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/courses/create-individual" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"course\": {
    \"name\": \"string\",
    \"sectionId\": \"00000000-0000-4000-8000-000000000000\",
    \"seasonId\": \"00000000-0000-4000-8000-000000000000\",
    \"staffMemberId\": null,
    \"resourceId\": null,
    \"minCapacity\": null,
    \"ageFrom\": null,
    \"ageTo\": null,
    \"lifecycleStatus\": \"draft\",
    \"description\": null,
    \"defaultBillingPlanId\": null,
    \"k07Category\": null
  },
  \"participantId\": \"00000000-0000-4000-8000-000000000000\",
  \"scheduleLine\": {
    \"weekday\": 1,
    \"startsAt\": \"string\",
    \"endsAt\": \"string\",
    \"resourceId\": null,
    \"staffMemberId\": null,
    \"validFrom\": null,
    \"validTo\": null
  },
  \"source\": \"panel\",
  \"startsOn\": null,
  \"billingPlanId\": null,
  \"payerContactId\": null
}"
```

## GET `/hoopi_classes/enrollments`

Lista Zapisów albo jeden Zapis z historią (`?id=`)

Filtry `courseId`, `participantId`, `status`. Każdy wiersz `waitlisted` niesie `position` — rangę w kolejce liczoną po `submitted_at`. `?id=` zwraca `{ item, history }` z pełną Historią statusów.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| courseId | query | any | Optional |
| participantId | query | any | Optional |
| status | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |

### Responses

**200** – Lista Zapisów

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "participant_id": "00000000-0000-4000-8000-000000000000",
      "course_id": "00000000-0000-4000-8000-000000000000",
      "status": "string",
      "starts_on": null,
      "ends_on": null,
      "submitted_at": "string",
      "submitted_by_contact_id": null,
      "source": "string",
      "billing_plan_id": null,
      "payer_contact_id": null,
      "offer_expires_at": null,
      "updated_at": "string",
      "position": null
    }
  ],
  "totalCount": 1
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Zapis nie istnieje w tym zakresie (`?id=`)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/enrollments?page=1&pageSize=50&sortDir=asc&sortField=submitted_at" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/enrollments`

Tworzy Zapis — serwer decyduje active/waitlisted

Jedno wywołanie. Komplet miejsc nie jest błędem: 201 z `status: "waitlisted"` i `position`, nie 409. Transakcja z blokadą wiersza Kursu.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "participantId": "00000000-0000-4000-8000-000000000000",
  "courseId": "00000000-0000-4000-8000-000000000000",
  "source": "panel",
  "startsOn": null,
  "billingPlanId": null,
  "payerContactId": null
}
```

### Responses

**201** – Zapis utworzony

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "status": "string"
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Zajęcia nie istnieją w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Uczestnik ma już otwarty Zapis na te Zajęcia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/enrollments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"participantId\": \"00000000-0000-4000-8000-000000000000\",
  \"courseId\": \"00000000-0000-4000-8000-000000000000\",
  \"source\": \"panel\",
  \"startsOn\": null,
  \"billingPlanId\": null,
  \"payerContactId\": null
}"
```

## POST `/hoopi_classes/enrollments/{id}/transition`

Zmienia status Zapisu wg maszyny stanów

Wymaga `updatedAt` (blokada optymistyczna). Blokuje wiersz Kursu na czas przejścia i zamiata wygasłe oferty tego Kursu jako pierwszy krok. `waitlisted → offered` odmawia (409) bez miejsca; `pending → active` bez miejsca ląduje na `waitlisted`, nie odmawia.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Zapisu |

### Request Body

Content-Type: `application/json`

```json
{
  "toStatus": "pending",
  "reason": null,
  "updatedAt": "string"
}
```

### Responses

**200** – Przejście wykonane

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "fromStatus": "string",
  "toStatus": "string"
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Zapis albo powiązane Zajęcia nie istnieją w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Przejście niedozwolone, brak miejsca przy `waitlisted → offered`, albo konflikt wersji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/enrollments/00000000-0000-4000-8000-000000000000/transition" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"toStatus\": \"pending\",
  \"reason\": null,
  \"updatedAt\": \"string\"
}"
```

## DELETE `/hoopi_classes/grades`

Delete ocena

Usuwa ocenę miękko.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Ocena deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_classes/grades" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/grades`

List oceny

Zwraca stronicowaną listę: oceny. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| participantId | query | any | Optional |
| courseId | query | any | Optional |
| category | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated oceny

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "participant_id": null,
      "course_id": null,
      "session_id": null,
      "kind": null,
      "value": null,
      "category": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/grades?page=1&pageSize=50&sortDir=asc&sortField=created_at" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/grades`

Create ocena

`value` szyfrowana bez względu na `kind`. `sessionId` opcjonalne — ocena za cały kurs.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "participantId": "00000000-0000-4000-8000-000000000000",
  "courseId": "00000000-0000-4000-8000-000000000000",
  "sessionId": null,
  "kind": "numeric",
  "value": "string",
  "category": null
}
```

### Responses

**201** – Ocena created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/grades" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"participantId\": \"00000000-0000-4000-8000-000000000000\",
  \"courseId\": \"00000000-0000-4000-8000-000000000000\",
  \"sessionId\": null,
  \"kind\": \"numeric\",
  \"value\": \"string\",
  \"category\": null
}"
```

## PUT `/hoopi_classes/grades`

Update ocena

Uczestnika i Zajęć nie da się podmienić — to byłaby inna ocena.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "category": null
}
```

### Responses

**200** – Ocena updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/grades" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"category\": null
}"
```

## GET `/hoopi_classes/guardians`

Osoby, które są opiekunem co najmniej jednego dziecka w organizacji

Stronicowane po osobie, najnowsi pierwsi. Wyszukiwanie przez indeks tokenów (kolumny osoby są szyfrowane).

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |

### Responses

**200** – Lista opiekunów

Content-Type: `application/json`

```json
{
  "items": [
    {
      "contactId": "00000000-0000-4000-8000-000000000000",
      "displayName": "string",
      "firstName": null,
      "lastName": null,
      "phone": null,
      "email": null,
      "updatedAt": null,
      "children": [
        {
          "participantId": "00000000-0000-4000-8000-000000000000",
          "firstName": "string",
          "lastName": "string",
          "guardianshipId": "00000000-0000-4000-8000-000000000000",
          "role": "string"
        }
      ]
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/guardians?page=1&pageSize=25" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/guardians`

Zakłada osobę i powiązanie z dzieckiem albo podpina istniejącą osobę

Nowa osoba: imię, nazwisko, telefon i e-mail obowiązkowe. Podpięcie: `existingContactId` (wyłącznie osoba z tej organizacji). Pierwszy opiekun dziecka dostaje domyślnie płatnika i kontakt główny; zgody na kontakt, odbiór i opiekę prawną startują wyłączone. Gdy powiązanie się nie zapisze, świeżo założona osoba jest usuwana.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "participantId": "00000000-0000-4000-8000-000000000000",
  "role": "guardian",
  "existingContactId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**201** – Opiekun dodany

Content-Type: `application/json`

```json
{
  "guardianshipId": "00000000-0000-4000-8000-000000000000",
  "contactId": "00000000-0000-4000-8000-000000000000",
  "contactCreated": true
}
```

**400** – Błędne dane

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Uczestnik albo osoba nie istnieje w tej organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Ta osoba jest już opiekunem tego dziecka

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/guardians" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"participantId\": \"00000000-0000-4000-8000-000000000000\",
  \"role\": \"guardian\",
  \"existingContactId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/guardians/{contactId}`

Dane opiekuna i jego dzieci w organizacji

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| contactId | path | any | Required. Identyfikator osoby (kontaktu CRM) |

### Responses

**200** – Opiekun

Content-Type: `application/json`

```json
{
  "contactId": "00000000-0000-4000-8000-000000000000",
  "displayName": "string",
  "firstName": null,
  "lastName": null,
  "phone": null,
  "email": null,
  "updatedAt": null,
  "children": [
    {
      "participantId": "00000000-0000-4000-8000-000000000000",
      "firstName": "string",
      "lastName": "string",
      "guardianshipId": "00000000-0000-4000-8000-000000000000",
      "role": "string"
    }
  ]
}
```

**404** – Osoba nie jest opiekunem w tej organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/guardians/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/hoopi_classes/guardians/{contactId}`

Zmiana imienia, nazwiska, telefonu i e-maila opiekuna

Zapis przez `customers.people.update`. Zmiana dotyczy wszystkich dzieci tej osoby.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| contactId | path | any | Required. Identyfikator osoby (kontaktu CRM) |

### Request Body

Content-Type: `application/json`

```json
{
  "firstName": "string",
  "lastName": "string",
  "phone": "string",
  "email": "user@example.com",
  "updatedAt": null
}
```

### Responses

**200** – Zapisano

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Błędne dane

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Osoba nie jest opiekunem w tej organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Dane zmieniły się w międzyczasie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/guardians/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"firstName\": \"string\",
  \"lastName\": \"string\",
  \"phone\": \"string\",
  \"email\": \"user@example.com\",
  \"updatedAt\": null
}"
```

## GET `/hoopi_classes/guardians/candidates`

Istniejące osoby o tym samym telefonie albo e-mailu — do podpięcia zamiast duplikatu

Dopasowanie wyłącznie po odciskach. Tylko osoby, najwyżej trzy, telefon i e-mail zamaskowane. Bez skonfigurowanego peppera zwraca `degraded: true` i pustą listę.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| phone | query | any | Optional |
| email | query | any | Optional |
| participantId | query | any | Optional |

### Responses

**200** – Kandydaci

Content-Type: `application/json`

```json
{
  "degraded": true,
  "items": [
    {
      "contactId": "00000000-0000-4000-8000-000000000000",
      "displayName": "string",
      "maskedPhone": null,
      "maskedEmail": null,
      "children": [
        "string"
      ],
      "alreadyLinked": true
    }
  ]
}
```

**400** – Brak telefonu i e-maila

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/guardians/candidates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/hoopi_classes/guardianships`

Delete powiązanie

Usuwa Powiązanie miękko.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Powiązanie deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_classes/guardianships" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/guardianships`

List powiązania

Zwraca stronicowaną listę: powiązania. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| participantId | query | any | Optional |
| contactId | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated powiązania

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "participant_id": null,
      "contact_id": null,
      "role": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/guardianships?page=1&pageSize=50&sortDir=asc&sortField=created_at" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/guardianships`

Create powiązanie

Wiąże kontakt z Uczestnikiem. Ta sama osoba przy tym samym dziecku drugi raz zwraca 409; ustawienie kontaktu głównego zdejmuje flagę z poprzedniego.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "participantId": "00000000-0000-4000-8000-000000000000",
  "contactId": "00000000-0000-4000-8000-000000000000",
  "role": "mother"
}
```

### Responses

**201** – Powiązanie created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/guardianships" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"participantId\": \"00000000-0000-4000-8000-000000000000\",
  \"contactId\": \"00000000-0000-4000-8000-000000000000\",
  \"role\": \"mother\"
}"
```

## PUT `/hoopi_classes/guardianships`

Update powiązanie

Zmienia rolę i flagi Powiązania. Uczestnika ani kontaktu nie da się podmienić — to byłoby inne Powiązanie.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Powiązanie updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/guardianships" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## DELETE `/hoopi_classes/materials`

Usuwa materiał

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Usunięto

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**404** – Materiał nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_classes/materials" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/materials`

Lista materiałów Terminu

**Tags:** Zajęcia

**Requires authentication.**

### Responses

**200** – Materiały Terminu

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "session_id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "url": "string",
      "created_by_user_id": null,
      "updated_at": "string"
    }
  ],
  "totalCount": 1
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/materials" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/materials`

Dodaje materiał do Terminu

Puste `name` zapisuje się jako „Materiał z lekcji". `url` musi mieć schemat http(s)://.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "url": "string"
}
```

### Responses

**201** – Materiał utworzony

Content-Type: `application/json`

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

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Termin nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/materials" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"sessionId\": \"00000000-0000-4000-8000-000000000000\",
  \"url\": \"string\"
}"
```

## DELETE `/hoopi_classes/participants`

Delete uczestnik

Usuwa Uczestnika miękko. Uczestnik z żywym Powiązaniem zwraca 409.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Uczestnik deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_classes/participants" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/participants`

List uczestnicy

Zwraca stronicowaną listę: uczestnicy. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |
| q | query | any | Optional |
| activity | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |

### Responses

**200** – Paginated uczestnicy

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "first_name": null,
      "last_name": null,
      "birth_date": null,
      "notes": null,
      "anonymized_at": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/participants?page=1&pageSize=50&sortDir=asc&sortField=last_name" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/participants`

Create uczestnik

Tworzy Uczestnika. Imię, nazwisko i notatka są szyfrowane po stronie najemcy. Data urodzenia z przyszłości kończy się 400.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "firstName": "string",
  "lastName": "string",
  "birthDate": "string",
  "notes": null
}
```

### Responses

**201** – Uczestnik created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/participants" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"firstName\": \"string\",
  \"lastName\": \"string\",
  \"birthDate\": \"string\",
  \"notes\": null
}"
```

## PUT `/hoopi_classes/participants`

Update uczestnik

Zmienia Uczestnika. `updatedAt` włącza blokadę optymistyczną.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Uczestnik updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/participants" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"notes\": null
}"
```

## GET `/hoopi_classes/progress-notes`

Lista notatek o postępach

**Tags:** Zajęcia

**Requires authentication.**

### Responses

**200** – Notatki

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "participant_id": "00000000-0000-4000-8000-000000000000",
      "course_id": "00000000-0000-4000-8000-000000000000",
      "session_id": null,
      "transcript": null,
      "status": "string",
      "approved_at": null,
      "updated_at": "string"
    }
  ],
  "totalCount": 1
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/progress-notes" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/progress-notes`

Tworzy notatkę o postępach bez nagrania (poprawka tekstu)

Do wgrywania i transkrypcji nagrania służy `POST /progress-notes/transcribe`.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "participantId": "00000000-0000-4000-8000-000000000000",
  "courseId": "00000000-0000-4000-8000-000000000000",
  "sessionId": null,
  "transcript": null
}
```

### Responses

**201** – Notatka utworzona

Content-Type: `application/json`

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

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Zajęcia albo Termin nie istnieją w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/progress-notes" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"participantId\": \"00000000-0000-4000-8000-000000000000\",
  \"courseId\": \"00000000-0000-4000-8000-000000000000\",
  \"sessionId\": null,
  \"transcript\": null
}"
```

## POST `/hoopi_classes/progress-notes/{id}/approve`

Przejście draft → approved

Jedyne przejście stanu, bez drogi powrotnej. Wymaga `updatedAt` (blokada optymistyczna).

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator notatki |

### Request Body

Content-Type: `application/json`

```json
{
  "updatedAt": "string"
}
```

### Responses

**200** – Notatka zatwierdzona

Content-Type: `application/json`

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

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Notatka nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Notatka nie jest w stanie `draft`, albo konflikt wersji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/progress-notes/00000000-0000-4000-8000-000000000000/approve" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"updatedAt\": \"string\"
}"
```

## GET `/hoopi_classes/progress-notes/{id}/audio`

Zwraca bajty nagrania — jedyna droga do audio

Sprawdza `gradebook.view` i zawężenie instruktora na Notatce przed sięgnięciem po plik.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator notatki |

### Responses

**200** – Nagranie audio

Content-Type: `application/json`

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Notatka, nagranie albo plik nie istnieją w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/progress-notes/00000000-0000-4000-8000-000000000000/audio" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/progress-notes/transcribe`

Wgrywa nagranie, transkrybuje je i tworzy notatkę `draft`

multipart/form-data: pole `audio` (plik), `participantId`, `courseId`, opcjonalnie `sessionId`. Klucz dostawcy transkrypcji nie opuszcza serwera. Awaria dostawcy nie blokuje zapisu — notatka powstaje z pustą transkrypcją.

**Tags:** Zajęcia

**Requires authentication.**

### Responses

**201** – Notatka utworzona

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "transcript": null,
  "transcriptionFailed": true
}
```

**400** – Brak pliku audio albo błędne pola

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Zajęcia albo Termin nie istnieją w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**413** – Nagranie przekracza limit rozmiaru

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/progress-notes/transcribe" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/hoopi_classes/room-conflicts`

Pary Zajęć dzielące jedną Salę tego samego dnia o nakładających się godzinach

Raport wyłącznie do odczytu: dwa żywe Terminy różnych Zajęć w tej samej Sali, tego samego dnia, o godzinach nakładających się ostro (styk nie jest kolizją). Jednostka to para Terminów — pozycja listy grupuje wszystkie wystąpienia pary Zajęć w danej Sali. Podaj `seasonId` (granica jednego Sezonu) albo `from`/`to` (najwyżej 62 dni, ten sam limit co `GET /calendar`) — nigdy oba naraz. Wymaga `hoopi_classes.view_all`: kolizja jest parą, więc zawężenie po instruktorze albo przecieka nazwę cudzych Zajęć, albo pokazuje kolizję bez drugiej strony.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| seasonId | query | any | Optional |
| from | query | any | Optional |
| to | query | any | Optional |

### Responses

**200** – Pozycje kolizji, posortowane po nazwie Sali, potem pierwszej dacie

Content-Type: `application/json`

```json
{
  "seasonId": null,
  "total": 1,
  "items": [
    {
      "resourceId": "00000000-0000-4000-8000-000000000000",
      "resourceName": null,
      "courseA": {
        "id": "00000000-0000-4000-8000-000000000000",
        "name": "string"
      },
      "courseB": {
        "id": "00000000-0000-4000-8000-000000000000",
        "name": "string"
      },
      "count": 1,
      "firstDate": "string",
      "lastDate": "string",
      "occurrences": [
        {
          "date": "string",
          "a": {
            "sessionId": "00000000-0000-4000-8000-000000000000",
            "startsAt": "string",
            "endsAt": "string"
          },
          "b": {
            "sessionId": "00000000-0000-4000-8000-000000000000",
            "startsAt": "string",
            "endsAt": "string"
          }
        }
      ]
    }
  ],
  "outsideAvailability": {
    "sessions": [
      {
        "id": "00000000-0000-4000-8000-000000000000",
        "courseId": "00000000-0000-4000-8000-000000000000",
        "courseName": "string",
        "resourceId": "00000000-0000-4000-8000-000000000000",
        "roomName": null,
        "startsAt": "string",
        "endsAt": "string",
        "date": "string"
      }
    ],
    "lines": [
      {
        "id": "00000000-0000-4000-8000-000000000000",
        "courseId": "00000000-0000-4000-8000-000000000000",
        "courseName": "string",
        "resourceId": "00000000-0000-4000-8000-000000000000",
        "roomName": null,
        "startsAt": "string",
        "endsAt": "string",
        "weekday": 1
      }
    ]
  }
}
```

**400** – Brak `seasonId`/`from`+`to`, oba naraz, zły zakres dat, Sezon spoza zakresu albo brak kontekstu organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak uprawnienia `hoopi_classes.view_all`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/room-conflicts" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/hoopi_classes/rooms`

Sale organizacji z Lokalizacją i dostępnością

Sale (zasoby `resources`) organizacji — także bez Lokalizacji — z nazwą, opisem, Lokalizacją i oknami dostępności. `ids` (rozdzielone przecinkiem) czyta wiele sal jednym zapytaniem, `venueId` zawęża do Lokalizacji, `search` szuka po nazwie. Wymaga `hoopi_classes.view` — nie `resources.view`.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| venueId | query | any | Optional |
| ids | query | any | Optional |
| id | query | any | Optional |
| search | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |

### Responses

**200** – Strona listy Sal, po nazwie

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "description": null,
      "venueId": null,
      "venueName": null,
      "venueRoomId": null,
      "windows": [
        {
          "weekday": 1,
          "startsAt": "string",
          "endsAt": "string"
        }
      ],
      "updatedAt": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
```

**400** – Błędne parametry albo brak kontekstu organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy `hoopi_classes.view`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/rooms?page=1&pageSize=100" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/rooms`

Zakłada Salę w Lokalizacji jednym formularzem

Zakłada zasób `resources` (nazwa, opis), przypisuje go do Lokalizacji i zapisuje okna dostępności (puste `windows` = pełna dostępność). Gdy przypisanie albo okna się nie zapiszą, świeżo założony zasób jest usuwany (kompensacja).

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "venueId": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "description": null,
  "windows": []
}
```

### Responses

**201** – Sala założona; `id` to identyfikator zasobu

Content-Type: `application/json`

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

**400** – Błędne wejście (nazwa, okna)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy `hoopi_classes.manage`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Lokalizacja nie istnieje w tej organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Sala o tej nazwie już jest w Lokalizacji (`hoopi_classes.room_name_taken`)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/rooms" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"venueId\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\",
  \"description\": null,
  \"windows\": []
}"
```

## GET `/hoopi_classes/rooms/{id}`

Jedna Sala z Lokalizacją i dostępnością

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Sali (zasobu `resources`) |

### Responses

**200** – Sala

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "description": null,
  "venueId": null,
  "venueName": null,
  "venueRoomId": null,
  "windows": [
    {
      "weekday": 1,
      "startsAt": "string",
      "endsAt": "string"
    }
  ],
  "updatedAt": null
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy `hoopi_classes.view`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Sala nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/rooms/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/hoopi_classes/rooms/{id}`

Zmienia nazwę, opis i dostępność Sali

Z `name` — nazwa, opis i okna; bez `name` — wyłącznie okna. Puste `windows` = pełna dostępność. Zapis dostępności przechodzi także wtedy, gdy istniejące Terminy wypadają poza nowe okno — blokada dotyczy nowych zapisów Terminów i linii.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Sali (zasobu `resources`) |

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "description": null,
  "windows": [
    {
      "weekday": 1,
      "startsAt": "string",
      "endsAt": "string"
    }
  ]
}
```

### Responses

**200** – Sala zapisana

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy `hoopi_classes.manage`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Sala nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Sala zmieniła się w międzyczasie albo nazwa jest zajęta w Lokalizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/rooms/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"description\": null,
  \"windows\": [
    {
      \"weekday\": 1,
      \"startsAt\": \"string\",
      \"endsAt\": \"string\"
    }
  ]
}"
```

## DELETE `/hoopi_classes/schedule-lines`

Delete linia harmonogramu

Usuwa linię miękko wraz z przyszłymi Terminami `planned` bez Obecności. Przeszłość zostaje nietknięta; przyszły Termin z Obecnością zwraca 409.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Linia Harmonogramu deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_classes/schedule-lines" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/schedule-lines`

List linie harmonogramu

Zwraca stronicowaną listę: linie harmonogramu. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| courseId | query | any | Optional |
| enrollmentId | query | any | Optional |
| weekday | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated linie harmonogramu

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "course_id": null,
      "enrollment_id": null,
      "weekday": null,
      "starts_at": null,
      "ends_at": null,
      "resource_id": null,
      "staff_member_id": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/schedule-lines?page=1&pageSize=50&sortDir=asc&sortField=weekday" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/schedule-lines`

Create linia harmonogramu

Tworzy linię. Puste `enrollmentId` oznacza linię grupy, wypełnione — slot jednego ucznia. Zajęcia prowadzą albo grupę, albo tryb indywidualny: dołożenie linii drugiego rodzaju zwraca 409.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "courseId": "00000000-0000-4000-8000-000000000000",
  "enrollmentId": null,
  "weekday": 1,
  "startsAt": "string",
  "endsAt": "string",
  "resourceId": null,
  "staffMemberId": null,
  "validFrom": null,
  "validTo": null
}
```

### Responses

**201** – Linia Harmonogramu created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/schedule-lines" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"courseId\": \"00000000-0000-4000-8000-000000000000\",
  \"enrollmentId\": null,
  \"weekday\": 1,
  \"startsAt\": \"string\",
  \"endsAt\": \"string\",
  \"resourceId\": null,
  \"staffMemberId\": null,
  \"validFrom\": null,
  \"validTo\": null
}"
```

## PUT `/hoopi_classes/schedule-lines`

Update linia harmonogramu

Zmienia linię. Zajęć ani Zapisu zmienić się nie da. Istniejące Terminy nie są przeliczane — robi to dopiero generator.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "resourceId": null,
  "staffMemberId": null,
  "validFrom": null,
  "validTo": null
}
```

### Responses

**200** – Linia Harmonogramu updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/schedule-lines" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"resourceId\": null,
  \"staffMemberId\": null,
  \"validFrom\": null,
  \"validTo\": null
}"
```

## POST `/hoopi_classes/schedule-lines/{id}/move`

Zamyka albo kasuje starą linię i otwiera nową od `from`, z Terminami tylko dla niej

Jedna transakcja: kolizja godzin (409), kaskada przyszłych Terminów starej linii od `from` (409 przy Obecności), zamknięcie/skasowanie starej linii, nowa linia i jej Terminy przez rozszerzony generator. `dryRun: true` liczy skutek bez zapisu, bez transakcji i bez wiersza audytu.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator starej Linii Harmonogramu |

### Request Body

Content-Type: `application/json`

```json
{
  "from": "string",
  "weekday": 1,
  "startsAt": "string",
  "endsAt": "string",
  "resourceId": null,
  "updatedAt": "string",
  "dryRun": false
}
```

### Responses

**200** – Podgląd (`dryRun`) albo wynik zapisu

Content-Type: `application/json`

```json
{
  "from": "string",
  "removedCount": 1,
  "createdCount": 1,
  "firstNewDate": null,
  "oldLineVariant": "closed"
}
```

**400** – Błędne wejście, `move_in_past`, poza Sezonem, albo `generation_too_large`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Linia nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Konflikt wersji, kolizja godzin z inną linią, albo Obecność w zasięgu kaskady

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/schedule-lines/00000000-0000-4000-8000-000000000000/move" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"from\": \"string\",
  \"weekday\": 1,
  \"startsAt\": \"string\",
  \"endsAt\": \"string\",
  \"resourceId\": null,
  \"updatedAt\": \"string\",
  \"dryRun\": false
}"
```

## DELETE `/hoopi_classes/seasons`

Delete sezon

Usuwa Sezon miękko. Sezon z żywymi Zajęciami jest zablokowany i zwraca 409.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Sezon deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_classes/seasons" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/seasons`

List sezony

Zwraca stronicowaną listę: sezony. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| isDefault | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated sezony

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "starts_on": null,
      "ends_on": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/seasons?page=1&pageSize=50&sortDir=asc&sortField=starts_on" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/seasons`

Create sezon

Tworzy Sezon. Ustawienie domyślności zdejmuje ją z pozostałych Sezonów organizacji.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "startsOn": "string",
  "endsOn": "string"
}
```

### Responses

**201** – Sezon created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/seasons" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"startsOn\": \"string\",
  \"endsOn\": \"string\"
}"
```

## PUT `/hoopi_classes/seasons`

Update sezon

Zmienia Sezon. Wysłanie `updatedAt` włącza blokadę optymistyczną i daje 409 przy konflikcie.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Sezon updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/seasons" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/hoopi_classes/seasons/{id}/closure-days/import`

Importuje święta ustawowe i ferie zimowe (oba org-wide) w oknie Sezonu

Idempotentne po `import_key`. Żywy nietknięty wiersz o innych danych dostaje `updated`, identyczny — `alreadyPresent`, skasowany nietknięty wraca jako `restored`, `touched` jest pomijany jako `skippedTouched`. Filia bez wpisu w pliku ferii dla wyliczonego roku szkolnego trafia do `noDataFor`. Cofalne — miękko kasuje `created`/`restored`, przywraca poprzednią treść `updated`, pomija `touched` bez raportu.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Sezonu |

### Responses

**200** – Raport importu

Content-Type: `application/json`

```json
{
  "created": 1,
  "updated": 1,
  "restored": 1,
  "alreadyPresent": 1,
  "skippedTouched": 1,
  "noDataFor": [
    {
      "schoolYear": "string",
      "voivodeship": "string",
      "branchName": null
    }
  ]
}
```

**400** – Błędne wejście albo brak kontekstu organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Sezon nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/seasons/00000000-0000-4000-8000-000000000000/closure-days/import" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/seasons/{id}/closure-days/mark-all-imported-blocking`

Ustawia blocks_sessions=true na zaimportowanych Dniach wolnych w oknie Sezonu

Dotyczy wyłącznie wierszy `source: 'import'` z `blocks_sessions=false`, których przedział zachodzi na okno Sezonu, w całej organizacji. Ustawia `touched=true` na każdym zmienionym wierszu. Cofalne.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Sezonu |

### Responses

**200** – Liczba zmienionych wierszy

Content-Type: `application/json`

```json
{
  "updatedCount": 1
}
```

**400** – Błędne wejście albo brak kontekstu organizacji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Sezon nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/seasons/00000000-0000-4000-8000-000000000000/closure-days/mark-all-imported-blocking" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/hoopi_classes/sections`

Delete kurs

Usuwa Kurs miękko. Kurs z Zajęciami albo Kursami podrzędnymi zwraca 409.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Kurs deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_classes/sections" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/sections`

List kursy

Zwraca stronicowaną listę: kursy. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| parentId | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated kursy

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "parent_id": null,
      "k07_category": null,
      "venue_id": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/sections?page=1&pageSize=50&sortDir=asc&sortField=name" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/sections`

Create kurs

Tworzy Kurs. Drzewo ma najwyżej trzy poziomy; wskazanie rodzica spoza organizacji kończy się 400.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "parentId": null,
  "k07Category": null,
  "venueId": null
}
```

### Responses

**201** – Kurs created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/sections" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"parentId\": null,
  \"k07Category\": null,
  \"venueId\": null
}"
```

## PUT `/hoopi_classes/sections`

Update kurs

Zmienia Kurs. Przeniesienie do własnego poddrzewa lub poniżej trzeciego poziomu kończy się 400; `updatedAt` włącza blokadę optymistyczną.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "parentId": null,
  "k07Category": null,
  "venueId": null
}
```

### Responses

**200** – Kurs updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/sections" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"parentId\": null,
  \"k07Category\": null,
  \"venueId\": null
}"
```

## GET `/hoopi_classes/sessions`

List terminy

Zwraca stronicowaną listę: terminy. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| courseId | query | any | Optional |
| scheduleLineId | query | any | Optional |
| staffMemberId | query | any | Optional |
| generationRunId | query | any | Optional |
| status | query | any | Optional |
| from | query | any | Optional |
| to | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated terminy

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "course_id": null,
      "schedule_line_id": null,
      "enrollment_id": null,
      "generation_run_id": null,
      "date": null,
      "starts_at": null,
      "ends_at": null,
      "resource_id": null,
      "staff_member_id": null,
      "status": null,
      "topic": null,
      "homework": null,
      "homework_due_on": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/sessions?page=1&pageSize=50&sortDir=asc&sortField=date" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/sessions`

Dodaje Termin ręczny

Termin ręczny (`schedule_line_id = NULL`, `generation_run_id = NULL`). Projekcja Udziałów `regular` biegnie w tej samej transakcji.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "courseId": "00000000-0000-4000-8000-000000000000",
  "date": "string",
  "startsAt": "string",
  "endsAt": "string",
  "resourceId": null,
  "staffMemberId": null
}
```

### Responses

**201** – Termin utworzony

Content-Type: `application/json`

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

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Zajęcia nie istnieją w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Termin ręczny o tej dacie i godzinie już istnieje

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/sessions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"courseId\": \"00000000-0000-4000-8000-000000000000\",
  \"date\": \"string\",
  \"startsAt\": \"string\",
  \"endsAt\": \"string\",
  \"resourceId\": null,
  \"staffMemberId\": null
}"
```

## POST `/hoopi_classes/sessions/{id}`

Zmienia godziny, salę albo prowadzącego Terminu

Wymaga `updatedAt` (blokada optymistyczna). Zmiana `staffMemberId` na inny niż kursowy jest zastępstwem — sam zapis wartości wystarcza. Odmawia na Terminie `held` i na statusach martwych (`cancelled`, `rescheduled`).

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Terminu |

### Request Body

Content-Type: `application/json`

```json
{
  "resourceId": null,
  "staffMemberId": null,
  "updatedAt": "string"
}
```

### Responses

**200** – Termin zaktualizowany

Content-Type: `application/json`

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

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Termin nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Konflikt wersji, albo Termin `held`/statusu martwego niepodlegający edycji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/sessions/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"resourceId\": null,
  \"staffMemberId\": null,
  \"updatedAt\": \"string\"
}"
```

## POST `/hoopi_classes/sessions/{id}/attendance`

Odhacza Obecność wielu Udziałów naraz

Wpis bez `updatedAt` tworzy wiersz Obecności; z `updatedAt` aktualizuje istniejący z blokadą optymistyczną. Pierwsze odhaczenie przestawia Termin z `planned` na `held`, w tej samej transakcji.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Terminu |

### Request Body

Content-Type: `application/json`

```json
{
  "entries": [
    {
      "ticketId": "00000000-0000-4000-8000-000000000000",
      "presence": "present",
      "settlement": "none",
      "note": null
    }
  ]
}
```

### Responses

**200** – Obecność zapisana

Content-Type: `application/json`

```json
{
  "sessionId": "00000000-0000-4000-8000-000000000000",
  "sessionStatus": "string",
  "entries": [
    {
      "ticket_id": "00000000-0000-4000-8000-000000000000",
      "attendance_record_id": "00000000-0000-4000-8000-000000000000",
      "presence": "string",
      "settlement": "string",
      "marked_at": null
    }
  ]
}
```

**400** – Błędne wejście albo zakazana kombinacja presence/settlement

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak uprawnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Termin nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Brak Udziału, duplikat wiersza Obecności albo konflikt wersji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/sessions/00000000-0000-4000-8000-000000000000/attendance" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entries\": [
    {
      \"ticketId\": \"00000000-0000-4000-8000-000000000000\",
      \"presence\": \"present\",
      \"settlement\": \"none\",
      \"note\": null
    }
  ]
}"
```

## POST `/hoopi_classes/sessions/{id}/cancel`

Odwołuje Termin

Udziały zostają — odwołanie nie kasuje projekcji. `held → cancelled` zabronione bez wyjątku. Korekta błędnie odhaczonego odrobienia idzie osobną trasą (`POST /attendance-tickets/[id]/revert-make-up`, Q-112), nie przez tę.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Terminu |

### Request Body

Content-Type: `application/json`

```json
{
  "cancelledBy": "organizer",
  "cancellationReason": "string",
  "updatedAt": "string"
}
```

### Responses

**200** – Termin odwołany

Content-Type: `application/json`

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

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Termin nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Termin `held` (zabronione) albo statusu martwego, albo konflikt wersji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/sessions/00000000-0000-4000-8000-000000000000/cancel" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"cancelledBy\": \"organizer\",
  \"cancellationReason\": \"string\",
  \"updatedAt\": \"string\"
}"
```

## POST `/hoopi_classes/sessions/{id}/lesson`

Zapisuje temat lekcji i pracę domową na Terminie

Cecha `gradebook.manage`. Bez blokady na `held` (temat wpisywany po odhaczeniu obecności jest przypadkiem normalnym); odmawia 409 na Terminie `cancelled`/`rescheduled`.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Terminu |

### Request Body

Content-Type: `application/json`

```json
{
  "topic": null,
  "homework": null,
  "homeworkDueOn": null
}
```

### Responses

**200** – Temat i praca domowa zapisane

Content-Type: `application/json`

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

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Termin nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Termin w statusie `cancelled`/`rescheduled` — niepodlegający edycji

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/sessions/00000000-0000-4000-8000-000000000000/lesson" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"topic\": null,
  \"homework\": null,
  \"homeworkDueOn\": null
}"
```

## POST `/hoopi_classes/sessions/{id}/reschedule`

Przekłada Termin na nową datę i godzinę

Źródłowy dostaje `rescheduled` i `rescheduled_to_session_id`; docelowy powstaje jako Termin ręczny. Udziały nie-`regular` przeniesione z zachowaniem `expires_on`; `regular` na docelowym daje projekcja. Wszystko w jednej transakcji.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required. Identyfikator Terminu źródłowego |

### Request Body

Content-Type: `application/json`

```json
{
  "date": "string",
  "startsAt": "string",
  "endsAt": "string",
  "resourceId": null,
  "staffMemberId": null,
  "updatedAt": "string"
}
```

### Responses

**200** – Termin przełożony

Content-Type: `application/json`

```json
{
  "sourceId": "00000000-0000-4000-8000-000000000000",
  "targetId": "00000000-0000-4000-8000-000000000000"
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Termin nie istnieje w tym zakresie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Konflikt wersji, Termin `held`/statusu martwego, albo kolizja z żywym Terminem ręcznym w dacie docelowej

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/sessions/00000000-0000-4000-8000-000000000000/reschedule" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"date\": \"string\",
  \"startsAt\": \"string\",
  \"endsAt\": \"string\",
  \"resourceId\": null,
  \"staffMemberId\": null,
  \"updatedAt\": \"string\"
}"
```

## GET `/hoopi_classes/settings`

Odczytuje ustawienia najemcy (województwo ferii, retencja nagrań e-dziennika)

**Tags:** Zajęcia

**Requires authentication.**

### Responses

**200** – Ustawienia najemcy

Content-Type: `application/json`

```json
{
  "winterBreakVoivodeship": null,
  "progressAudioRetentionDays": 1,
  "offerProfile": {
    "sectionsEnabled": true,
    "k07Enabled": true,
    "instructorsEnabled": true,
    "ownerStaffMemberId": null
  }
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/hoopi_classes/settings`

Zapisuje ustawienia najemcy (województwo ferii, retencja nagrań e-dziennika)

Bez wywołania importu dni wolnych — ekran woła go osobno, po udanym zapisie. `progressAudioRetentionDays` (domyślnie `0` = bezterminowo) nie egzekwuje się sam — patrz `POST /gradebook/sweep-progress-audio`.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "winterBreakVoivodeship": null
}
```

### Responses

**200** – Zapisane ustawienia organizacji

Content-Type: `application/json`

```json
{
  "winterBreakVoivodeship": null,
  "progressAudioRetentionDays": 1,
  "offerProfile": {
    "sectionsEnabled": true,
    "k07Enabled": true,
    "instructorsEnabled": true,
    "ownerStaffMemberId": null
  }
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"winterBreakVoivodeship\": null
}"
```

## DELETE `/hoopi_classes/venue-rooms`

Delete przypisanie sali

Odpina Salę od Lokalizacji. Sala sama w `resources` nie znika.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Przypisanie Sali deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_classes/venue-rooms" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/venue-rooms`

List przypisania sal

Zwraca stronicowaną listę: przypisania sal. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| venueId | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated przypisania sal

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "venue_id": null,
      "resource_id": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/venue-rooms?page=1&pageSize=50&sortDir=asc&sortField=created_at" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/venue-rooms`

Create przypisanie sali

Przypina Salę do Lokalizacji. Sala przypisana już gdzie indziej zwraca 409 `room_already_placed`.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "venueId": "00000000-0000-4000-8000-000000000000",
  "resourceId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**201** – Przypisanie Sali created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/venue-rooms" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"venueId\": \"00000000-0000-4000-8000-000000000000\",
  \"resourceId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## DELETE `/hoopi_classes/venues`

Delete lokalizacja

Usuwa Lokalizację miękko. Lokalizacja z przypisanymi Salami albo wskazywana przez Kurs zwraca 409.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Lokalizacja deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_classes/venues" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_classes/venues`

List lokalizacje

Zwraca stronicowaną listę: lokalizacje. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Zajęcia

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Optional |
| q | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| sortField | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated lokalizacje

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "address_line": null,
      "postal_code": null,
      "city": null,
      "country_code": null,
      "travel_notes": null,
      "tenant_id": null,
      "organization_id": null,
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_classes/venues?page=1&pageSize=50&sortDir=asc&sortField=name" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_classes/venues`

Create lokalizacja

Tworzy Lokalizację. Bez współrzędnych geograficznych — te wchodzą w Fazie 3 (geokoder).

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "addressLine": "string",
  "postalCode": "string",
  "city": "string",
  "travelNotes": null
}
```

### Responses

**201** – Lokalizacja created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_classes/venues" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"addressLine\": \"string\",
  \"postalCode\": \"string\",
  \"city\": \"string\",
  \"travelNotes\": null
}"
```

## PUT `/hoopi_classes/venues`

Update lokalizacja

Zmienia Lokalizację. `updatedAt` włącza blokadę optymistyczną.

**Tags:** Zajęcia

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "travelNotes": null
}
```

### Responses

**200** – Lokalizacja updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_classes/venues" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"travelNotes\": null
}"
```

## POST `/hoopi_setup/owner-instructor`

Zakłada (albo odnajduje) kartotekę prowadzącego dla konta wołającego

Idempotentne pod zamkiem doradczym na (najemca, organizacja, użytkownik): istniejąca osoba z `user_id` wołającego → `created: false`. E-mail zawsze z konta, nigdy z ciała. Imię i nazwisko z ciała wyłącznie wtedy, gdy nazwa konta nie rozdziela się na dwie części (inaczej 400 `hoopi_setup.owner_name_required` z `fieldErrors`). U solisty kartoteka bez weryfikacji i bez wynagrodzenia. Po zapisie profil oferty dostaje `ownerStaffMemberId`. Role użytkownika bez zmian.

**Tags:** Konfiguracja

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "phone": null
}
```

### Responses

**200** – Kartoteka z tym kontem już była

Content-Type: `application/json`

```json
{
  "staffMemberId": "00000000-0000-4000-8000-000000000000",
  "created": true,
  "linked": true
}
```

**201** – Kartoteka założona i konto podpięte

Content-Type: `application/json`

```json
{
  "staffMemberId": "00000000-0000-4000-8000-000000000000",
  "created": true,
  "linked": true
}
```

**400** – Brak imienia/nazwiska albo e-maila konta, obce pole w ciele

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy hoopi_setup.wizard.run albo hoopi_staff.manage

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Konto podpięte do innej osoby albo solista ma już prowadzącego

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_setup/owner-instructor" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"phone\": null
}"
```

## POST `/hoopi_setup/preset`

Zapisuje preset (solo/dk) i ustawia profil oferty w hoopi_classes

Idempotentne: ta sama wartość drugi raz zwraca `alreadyApplied: true` bez zmian. Inna wartość niż już zapisana → 409 `preset_conflict`. Schemat `.strict()` — obce pole w ciele → 400. Preset nie dotyka żadnej roli ani uprawnienia (REQ-006).

**Tags:** Konfiguracja

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "presetId": "solo"
}
```

### Responses

**200** – Preset zastosowany (albo już był)

Content-Type: `application/json`

```json
{
  "presetId": "solo",
  "alreadyApplied": true,
  "ownerInstructor": null
}
```

**400** – Nieznany preset lub obce pole

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy hoopi_setup.wizard.run

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Inny preset już wybrany dla tego najemcy

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_setup/preset" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"presetId\": \"solo\"
}"
```

## POST `/hoopi_setup/preset/undo`

Usuwa wpis presetu i postępu; profil oferty wraca do domyślnego

Ziarno (słownik K-07, domyślny Kurs) pozostaje nietknięte — to własność `hoopi_classes`, nie presetu. Dowolny kolejny preset przechodzi bez przeszkód.

**Tags:** Konfiguracja

**Requires authentication.**

### Responses

**200** – Preset cofnięty

Content-Type: `application/json`

```json
{
  "undone": true
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy hoopi_setup.wizard.run

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Kreator jest już w kroku Kursu/Zajęć (preset_used)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_setup/preset/undo" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_setup/progress`

Odnotowuje ukończony krok kreatora (zbiór kroków tylko rośnie)

Nowe wejście `{ stepId, dataSource?, importInterest?, refs? }`; stare `{ lastCompletedStep, …okruszki }` przyjmowane i tłumaczone tabelą wersji 4. 400 gdy krok nieznany, niewidoczny dla presetu, bez wymaganych okruszków (`step_dependencies_missing`), krok „Skąd dane" bez wyboru źródła (`data_source_required`), albo (stare wejście) numer spoza `0..6` lub mniejszy niż zapisany.

**Tags:** Konfiguracja

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "stepId": "preset"
}
```

### Responses

**200** – Stan kreatora po zapisie

Content-Type: `application/json`

```json
{
  "presetId": null,
  "lastCompletedStep": 1,
  "skipped": true,
  "completed": true,
  "nextStep": null,
  "completedSteps": [
    "preset"
  ],
  "newStepsSkipped": [
    "preset"
  ],
  "nextStepId": null,
  "visibleSteps": [
    "preset"
  ],
  "dataSource": null,
  "importInterestAt": null,
  "refs": {
    "venueId": null,
    "roomId": null,
    "ownerStaffMemberId": null,
    "seasonId": null,
    "sectionId": null,
    "courseId": null,
    "scheduleLineId": null,
    "participantIds": [
      "string"
    ]
  },
  "seasonId": null,
  "sectionId": null,
  "courseId": null,
  "scheduleLineId": null,
  "skippedAt": null
}
```

**400** – Krok nieznany, niewidoczny, bez zależności, bez wyboru źródła danych (`data_source_required`) albo cofnięcie postępu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy hoopi_setup.wizard.run

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_setup/progress" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"stepId\": \"preset\"
}"
```

## POST `/hoopi_setup/skip`

Dokończę później — zapisuje wyjście z kreatora; preset, profil oferty, ukończone kroki i okruszki pozostają niezmienione

**Tags:** Konfiguracja

**Requires authentication.**

### Responses

**200** – Pominięcie zapisane

Content-Type: `application/json`

```json
{
  "skippedAt": null
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy hoopi_setup.wizard.run

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_setup/skip" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/hoopi_setup/state`

Preset, postęp i ukończenie kreatora dla tego najemcy

`lastCompletedStep` i `nextStep` są przestarzałe (numeracja wersji 4); kroki niesie `visibleSteps`/`completedSteps`/`nextStepId`. Rozgałęziaj po `completed`, nie po `nextStep`.

**Tags:** Konfiguracja

**Requires authentication.**

### Responses

**200** – Stan kreatora

Content-Type: `application/json`

```json
{
  "presetId": null,
  "lastCompletedStep": 1,
  "skipped": true,
  "completed": true,
  "nextStep": null,
  "completedSteps": [
    "preset"
  ],
  "newStepsSkipped": [
    "preset"
  ],
  "nextStepId": null,
  "visibleSteps": [
    "preset"
  ],
  "dataSource": null,
  "importInterestAt": null,
  "refs": {
    "venueId": null,
    "roomId": null,
    "ownerStaffMemberId": null,
    "seasonId": null,
    "sectionId": null,
    "courseId": null,
    "scheduleLineId": null,
    "participantIds": [
      "string"
    ]
  },
  "seasonId": null,
  "sectionId": null,
  "courseId": null,
  "scheduleLineId": null
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy hoopi_setup.wizard.run

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_setup/state" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/hoopi_staff/instructor-profiles`

Delete kartoteka prowadzącego

Usuwa kartotekę razem z godzinami pracy. Kartoteka z choćby jednym wpisem weryfikacji zwraca 409 `hoopi_staff.profile_has_verifications` — historia sprawdzeń jest dowodem.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Kartoteka prowadzącego deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_staff/instructor-profiles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_staff/instructor-profiles`

List kartoteki prowadzących

Zwraca stronicowaną listę: kartoteki prowadzących. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| id | query | any | Optional |
| staffMemberId | query | any | Optional |
| search | query | any | Optional |
| sortField | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated kartoteki prowadzących

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "staff_member_id": null,
      "cooperation_from": null,
      "cooperation_to": null,
      "applicable_verification_kinds": null,
      "payroll_mode": null,
      "tenant_id": null,
      "organization_id": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/instructor-profiles?page=1&pageSize=50&sortDir=asc&sortField=createdAt" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_staff/instructor-profiles`

Create kartoteka prowadzącego

Zakłada kartotekę. Drugi wpis dla tego samego prowadzącego w organizacji zwraca 409 `hoopi_staff.profile_already_exists`. Pominięta lista obowiązków bierze wartość domyślną z ustawień organizacji.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "staffMemberId": "00000000-0000-4000-8000-000000000000",
  "cooperationFrom": null,
  "cooperationTo": null,
  "notes": null
}
```

### Responses

**201** – Kartoteka prowadzącego created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/instructor-profiles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"staffMemberId\": \"00000000-0000-4000-8000-000000000000\",
  \"cooperationFrom\": null,
  \"cooperationTo\": null,
  \"notes\": null
}"
```

## PUT `/hoopi_staff/instructor-profiles`

Update kartoteka prowadzącego

Zmienia daty współpracy, zakres obowiązków weryfikacyjnych, sposób rozliczenia i notatki. Prowadzącego nie da się przepiąć — to byłoby przeniesienie historii weryfikacji na cudze nazwisko.

**Tags:** Prowadzący

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "updatedAt": "string",
  "cooperationFrom": null,
  "cooperationTo": null,
  "notes": null
}
```

### Responses

**200** – Kartoteka prowadzącego updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_staff/instructor-profiles" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"updatedAt\": \"string\",
  \"cooperationFrom\": null,
  \"cooperationTo\": null,
  \"notes\": null
}"
```

## GET `/hoopi_staff/instructors`

Zwraca listę prowadzących z leniwie dołączoną kartoteką

Wierszem jest `StaffTeamMember`, nie nasza encja — osoba bez kartoteki wraca ze stanem `no_profile` („kartoteka nieuzupełniona"), który jest **neutralny**, nie ostrzeżeniem. Liczba Zajęć liczy się w Sezonie oznaczonym `is_default`, o ile nie podano `seasonId`. Dane osobowe czyta serwer, bo `GET staff/team-members` wymaga cechy `staff.view`, której sekretariat nie ma.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| state | query | any | Optional |
| activity | query | any | Optional |
| scope | query | any | Optional |
| seasonId | query | any | Optional |
| sort | query | any | Optional |
| sortDir | query | any | Optional |

### Responses

**200** – Strona listy

Content-Type: `application/json`

```json
{
  "items": [
    {}
  ],
  "total": 1,
  "today": "string",
  "seasonId": null,
  "warningDays": 1
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Organizacja nie udostępnia prowadzącym podglądu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/instructors?page=1&pageSize=50&scope=instructors&sort=state&sortDir=asc" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_staff/instructors`

Zakłada prowadzącego: wiersz kadrowy Open Mercato i kartotekę

Jedna komenda z kompensacją: najpierw `staff.team-members.create` z `displayName = "{imię} {nazwisko}"`, potem kartoteka z danymi kontaktowymi. Gdy kartoteka się nie zapisze, świeżo założony `StaffTeamMember` jest usuwany, a odpowiedź niesie błąd kartoteki. E-mail wymagany, telefon zapisywany w E.164.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "firstName": "string",
  "lastName": "string",
  "email": "user@example.com",
  "phone": null,
  "notes": null
}
```

### Responses

**201** – Założono

Content-Type: `application/json`

```json
{
  "staffMemberId": "00000000-0000-4000-8000-000000000000",
  "profileId": "00000000-0000-4000-8000-000000000000",
  "updatedAt": null
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy `hoopi_staff.manage` albo stawki w ciele bez `hoopi_staff.payroll.manage`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Stawka tego rodzaju już istnieje albo jest godzinowa; albo organizator prowadzi sam i ma już prowadzącego (`hoopi_staff.solo_single_instructor`)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/instructors" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"firstName\": \"string\",
  \"lastName\": \"string\",
  \"email\": \"user@example.com\",
  \"phone\": null,
  \"notes\": null
}"
```

## GET `/hoopi_staff/instructors/{id}`

Zwraca dane osoby, kartotekę, stan weryfikacji, historię i godziny pracy

`[id]` to identyfikator `StaffTeamMember`, nie kartoteki — osoba bez kartoteki musi dać się otworzyć, bo inaczej nie ma skąd jej założyć. Pole `profile` jest wtedy puste, a `verificationState` również.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Kartoteka

Content-Type: `application/json`

```json
{}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Organizacja nie udostępnia prowadzącym podglądu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono albo poza zakresem widoczności

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/instructors/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/hoopi_staff/instructors/{id}`

Zmienia dane podstawowe prowadzącego: osobę, kontakt, okres współpracy, zakres obowiązków i sposób rozliczenia

Kartoteka jest źródłem, `display_name` w Open Mercato pochodną: po zapisie kartoteki, gdy imię lub nazwisko nie zgadza się z `display_name`, trasa woła `staff.team-members.update`. Porażka tego kroku nie cofa kartoteki — wraca jako `displayNameSyncFailed: true`. Osoba bez kartoteki dostaje ją przy pierwszym zapisie. `updatedAt` kartoteki chroni przed nadpisaniem cudzej zmiany (409). Pola `cooperationFrom`, `cooperationTo`, `applicableVerificationKinds` i `payrollMode` są opcjonalne — pominięte zostają bez zmian, podane zapisują się tym samym zapisem wiersza, co dane osoby (atomowo). Koniec współpracy przed jej początkiem (także wobec zapisanej drugiej daty) zwraca 400 `hoopi_staff.cooperation_range_invalid`.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "firstName": "string",
  "lastName": "string",
  "email": "user@example.com",
  "phone": null,
  "notes": null,
  "cooperationFrom": null,
  "cooperationTo": null
}
```

### Responses

**200** – Zapisano

Content-Type: `application/json`

```json
{
  "ok": true,
  "profileId": "00000000-0000-4000-8000-000000000000",
  "updatedAt": null,
  "displayNameSyncFailed": true
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy `hoopi_staff.manage` albo stawki w ciele bez `hoopi_staff.payroll.manage`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono albo poza zakresem widoczności

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Kartoteka zmieniła się w międzyczasie

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_staff/instructors/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"firstName\": \"string\",
  \"lastName\": \"string\",
  \"email\": \"user@example.com\",
  \"phone\": null,
  \"notes\": null,
  \"cooperationFrom\": null,
  \"cooperationTo\": null
}"
```

## POST `/hoopi_staff/instructors/{id}/invite`

Zakłada konto z rolą `instructor`, wysyła zaproszenie i podpina konto do prowadzącego

Adres bierze się z kartoteki. `emailSent: false` znaczy, że konto powstało, ale mail nie wyszedł (wysyłka nieskonfigurowana) — ponowienie przez `POST /api/auth/users/resend-invite`. `linked: false` znaczy, że podpięcie padło po wysłaniu maila; kartoteka pokaże wtedy „Podepnij konto".

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**201** – Konto założone

Content-Type: `application/json`

```json
{
  "userId": "00000000-0000-4000-8000-000000000000",
  "emailSent": true,
  "linked": true
}
```

**403** – Brak `hoopi_staff.manage` lub `auth.users.create`, albo brak prawa nadania roli

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono prowadzącego

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Konto już jest (podpięte, z tym adresem albo w innej filii) lub kartoteka bez e-maila

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**503** – Wysyłka zaproszeń nieskonfigurowana (brak adresu aplikacji)

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/instructors/:id/invite" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_staff/instructors/{id}/link-account`

Ustawia `StaffTeamMember.user_id` na wskazane konto tej organizacji

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "userId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**200** – Podpięto

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**403** – Brak wymaganych cech

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono prowadzącego albo konta

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Prowadzący ma już inne konto albo konto należy do innej filii

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/instructors/:id/link-account" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"userId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_staff/instructors/{id}/timetable`

Zwraca Terminy własnych Zajęć oraz Terminy zastępstwa, w jednym zestawie

Dwie drogi z GWT-1 złożone w jeden terminarz: Termin oddany komuś innemu wypada, a Termin wracający obydwiema drogami nie dubluje się. Tylko do odczytu.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| from | query | any | Optional |
| to | query | any | Optional |

### Responses

**200** – Terminarz

Content-Type: `application/json`

```json
{
  "courses": [
    {
      "id": "string",
      "name": "string"
    }
  ],
  "sessions": [
    {}
  ]
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Organizacja nie udostępnia prowadzącym podglądu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono albo poza zakresem widoczności

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/instructors/:id/timetable" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/hoopi_staff/payroll-statements`

List podliczenia wynagrodzeń

Zwraca stronicowaną listę: podliczenia wynagrodzeń. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| id | query | any | Optional |
| staffMemberId | query | any | Optional |
| status | query | any | Optional |
| periodFrom | query | any | Optional |
| periodTo | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated podliczenia wynagrodzeń

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "staff_member_id": null,
      "staff_display_name": null,
      "period_from": null,
      "period_to": null,
      "total_minor": null,
      "currency": null,
      "status": null,
      "difference_minor": null,
      "supersedes_statement_id": null,
      "exported_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/payroll-statements?page=1&pageSize=50&sortDir=asc" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_staff/payroll-statements`

Create podliczenie wynagrodzenia

Wystawia podliczenie w stanie `draft` razem z Pozycjami. Nakładający się okres albo powtórzony token idempotencji zwracają **tę samą** odmowę 409 `hoopi_staff.statement_period_conflict` — kolejność sprawdzania ograniczeń w PostgreSQL nie jest kontraktem, więc kod błędu (`23505` czy `23P01`) nie jest częścią odpowiedzi.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "staffMemberId": "00000000-0000-4000-8000-000000000000",
  "periodFrom": "string",
  "periodTo": "string",
  "idempotencyKey": "string"
}
```

### Responses

**201** – Podliczenie wynagrodzenia created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/payroll-statements" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"staffMemberId\": \"00000000-0000-4000-8000-000000000000\",
  \"periodFrom\": \"string\",
  \"periodTo\": \"string\",
  \"idempotencyKey\": \"string\"
}"
```

## GET `/hoopi_staff/payroll-statements/{id}`

Zwraca nagłówek, Pozycje, sumę godzin i flagę nieaktualności szkicu

`stale` liczy się porównaniem zbioru `(session_id, amount_minor)`, nie sumy — zamiana obsady dwóch Terminów o tej samej stawce daje tę samą sumę. Dla dokumentu `issued` flaga jest zawsze `false`: zatwierdzenie zamraża.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Dokument

Content-Type: `application/json`

```json
{}
```

**403** – Organizacja nie udostępnia prowadzącym podglądu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono albo poza zakresem widoczności

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/payroll-statements/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_staff/payroll-statements/{id}/approve`

Zatwierdza podliczenie

Bierze to, co zapisano w Pozycjach — nie przelicza. Ponowne wywołanie na dokumencie `issued` jest bezskuteczne, nie błędne.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Wykonano

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Organizacja nie udostępnia prowadzącym podglądu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono albo poza zakresem widoczności

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Niedozwolone przejście stanu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/payroll-statements/:id/approve" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/hoopi_staff/payroll-statements/{id}/cancel`

Anuluje podliczenie z powodem

Powód jest wymagany i **szyfrowany**. `exported_at` zostaje nietknięte.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "updatedAt": "string",
  "reason": "string"
}
```

### Responses

**200** – Wykonano

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Organizacja nie udostępnia prowadzącym podglądu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono albo poza zakresem widoczności

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Niedozwolone przejście stanu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/payroll-statements/:id/cancel" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"updatedAt\": \"string\",
  \"reason\": \"string\"
}"
```

## POST `/hoopi_staff/payroll-statements/{id}/mark-exported`

Oznacza podliczenie jako przekazane do wypłaty

Wyłącznie dla dokumentu `issued`. Ponowne wywołanie nie przesuwa znacznika.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Wykonano

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Organizacja nie udostępnia prowadzącym podglądu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono albo poza zakresem widoczności

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Niedozwolone przejście stanu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/payroll-statements/:id/mark-exported" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/hoopi_staff/payroll-statements/{id}/recalculate`

Przelicza szkic podliczenia

Dokument `issued` albo `cancelled` zwraca 409 `hoopi_staff.statement_not_draft`.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Wykonano

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Organizacja nie udostępnia prowadzącym podglądu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono albo poza zakresem widoczności

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Niedozwolone przejście stanu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/payroll-statements/:id/recalculate" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/hoopi_staff/payroll-statements/{id}/replace`

Anuluje dokument i wystawia następcę z różnicą

Różnica liczy się wobec **najbliższego przodka z `exported_at`**, nie wobec bezpośredniego poprzednika — przy dwóch korektach z rzędu liczenie od poprzednika gubi ślad do kwoty, która naprawdę wyszła z organizacji.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "reason": "string",
  "idempotencyKey": "string"
}
```

### Responses

**200** – Wykonano

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Organizacja nie udostępnia prowadzącym podglądu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono albo poza zakresem widoczności

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Niedozwolone przejście stanu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/payroll-statements/:id/replace" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"reason\": \"string\",
  \"idempotencyKey\": \"string\"
}"
```

## POST `/hoopi_staff/payroll-statements/preview`

Liczy Pozycje i trzy listy ostrzegawcze bez zapisywania czegokolwiek

Ten sam kod liczący, co wystawienie — podgląd nie może się rozjechać z zapisem. Ostrzeżenia z dat współpracy **nie blokują**: rozliczenie ostatniego miesiąca wystawia się po odejściu prowadzącego.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "staffMemberId": "00000000-0000-4000-8000-000000000000",
  "periodFrom": "string",
  "periodTo": "string"
}
```

### Responses

**200** – Podgląd

Content-Type: `application/json`

```json
{}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Organizacja nie udostępnia prowadzącym podglądu

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Nie znaleziono albo poza zakresem widoczności

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/payroll-statements/preview" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"staffMemberId\": \"00000000-0000-4000-8000-000000000000\",
  \"periodFrom\": \"string\",
  \"periodTo\": \"string\"
}"
```

## GET `/hoopi_staff/payroll-summary`

Zwraca wiersz na prowadzącego, sekcję korekt i stopkę

Wymaga **`hoopi_staff.view_all`** jawnie: to jedyna trasa modułu pokazująca wielu prowadzących naraz, więc bez tej cechy instruktor zobaczyłby wynagrodzenia kolegów. Sekcja korekt chodzi po **całym łańcuchu** dokumentów, nie po jednym kroku.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| periodFrom | query | any | Required |
| periodTo | query | any | Required |

### Responses

**200** – Zestawienie

Content-Type: `application/json`

```json
{}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy `view_all`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/payroll-summary?periodFrom=string&periodTo=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_staff/payroll-summary/bulk`

Wystawia, przelicza, zatwierdza albo oznacza hurtem

Pętla po istniejących komendach, **osobna transakcja na osobę**. `dryRun` oddaje listę celów i pominiętych bez zapisu. Wystawianie pomija osoby z żywym dokumentem; zatwierdzanie pomija szkice nieaktualne i te z niewycenionymi Terminami — i wypisuje je imiennie.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "action": "issue",
  "periodFrom": "string",
  "periodTo": "string"
}
```

### Responses

**200** – Wynik per osoba

Content-Type: `application/json`

```json
{}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Brak cechy `view_all`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/payroll-summary/bulk" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"action\": \"issue\",
  \"periodFrom\": \"string\",
  \"periodTo\": \"string\"
}"
```

## GET `/hoopi_staff/payroll-summary/export`

Pobiera CSV do listy płac

Separator `;`, UTF-8 z BOM, CRLF, przecinek dziesiętny. Obejmuje dokumenty `issued` z tabeli okresu **oraz wszystkie wiersze sekcji korekt**, w tym nadpłatę w stanie `cancelled`. Pobranie **nie** ustawia `exported_at`.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| periodFrom | query | any | Required |
| periodTo | query | any | Required |

### Responses

**200** – Plik CSV

Content-Type: `application/json`

**403** – Brak cechy `view_all`

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/payroll-summary/export?periodFrom=string&periodTo=string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/hoopi_staff/rates`

Delete stawka prowadzącego

Miękko usuwa **pomyłkowo założoną** stawkę. Stawka, którą wyceniono choć jedną Pozycję podliczenia, zwraca 409 `hoopi_staff.rate_has_statement_items` — historia wypłat musi dać się odtworzyć.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Stawka prowadzącego deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_staff/rates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_staff/rates`

List stawki prowadzących

Zwraca stronicowaną listę: stawki prowadzących. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| id | query | any | Optional |
| staffMemberId | query | any | Optional |
| courseId | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated stawki prowadzących

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "staff_member_id": null,
      "course_id": null,
      "course_kind": null,
      "amount_minor": null,
      "unit": null,
      "currency": null,
      "valid_from": null,
      "supersedes_id": null,
      "root_rate_id": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/rates?page=1&pageSize=50&sortDir=asc" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_staff/rates`

Create stawka prowadzącego

Zakłada stawkę albo **nową wersję** istniejącej (`supersedesId`). Odmowy 409: `hoopi_staff.rate_chain_exists`, gdy para ma już stawkę (utwórz wersję, nie nową stawkę), i `hoopi_staff.rate_version_superseded`, gdy wskazana wersja ma już następcę. Edycji nie ma — wiersz jest niezmienny.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "staffMemberId": "00000000-0000-4000-8000-000000000000",
  "amountMinor": "string",
  "validFrom": "string"
}
```

### Responses

**201** – Stawka prowadzącego created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/rates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"staffMemberId\": \"00000000-0000-4000-8000-000000000000\",
  \"amountMinor\": \"string\",
  \"validFrom\": \"string\"
}"
```

## GET `/hoopi_staff/settings`

Odczytuje ustawienia organizacji

Brak wiersza ustawień oddaje wartości domyślne: podgląd własnej kartoteki **wyłączony**, próg ostrzegania 30 dni, domyślny obowiązek `rstpn_check`.

**Tags:** Prowadzący

**Requires authentication.**

### Responses

**200** – Ustawienia organizacji

Content-Type: `application/json`

```json
{
  "instructorSeesOwnProfile": true,
  "verificationWarningDays": 1,
  "defaultVerificationKinds": [
    "rstpn_check"
  ]
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/hoopi_staff/settings`

Zapisuje ustawienia organizacji

Zmiana `defaultVerificationKinds` **nie przepisuje istniejących kartotek** — dotyczy wyłącznie zakładanych po zmianie. Wyłączenie `instructorSeesOwnProfile` odcina prowadzącym każdą trasę modułu (403), mimo posiadanej cechy ACL.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Zapisane ustawienia

Content-Type: `application/json`

```json
{
  "instructorSeesOwnProfile": true,
  "verificationWarningDays": 1,
  "defaultVerificationKinds": [
    "rstpn_check"
  ]
}
```

**400** – Błędne wejście

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Brak uwierzytelnienia

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_staff/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## GET `/hoopi_staff/verifications`

List weryfikacje prowadzących

Zwraca stronicowaną listę: weryfikacje prowadzących. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| id | query | any | Optional |
| profileId | query | any | Optional |
| kind | query | any | Optional |
| result | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated weryfikacje prowadzących

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "profile_id": null,
      "kind": null,
      "verified_on": null,
      "result": null,
      "valid_until": null,
      "verified_by_user_id": null,
      "created_by_user_id": null,
      "created_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/verifications?page=1&pageSize=50&sortDir=desc" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_staff/verifications`

Create weryfikacja prowadzącego

Dopisuje wpis do historii sprawdzeń. Encja jest append-only — edycji i usunięcia nie ma, pomyłkę prostuje nowy wpis. Odmowy: 400 przy terminie ważności dla wyniku „zablokowany", przy terminie wcześniejszym niż dzień sprawdzenia i przy dacie sprawdzenia z przyszłości; 409, gdy rodzaj nie jest na liście obowiązków kartoteki.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "profileId": "00000000-0000-4000-8000-000000000000",
  "kind": "rstpn_check",
  "verifiedOn": "string",
  "result": "clear"
}
```

### Responses

**201** – Weryfikacja prowadzącego created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/verifications" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"profileId\": \"00000000-0000-4000-8000-000000000000\",
  \"kind\": \"rstpn_check\",
  \"verifiedOn\": \"string\",
  \"result\": \"clear\"
}"
```

## DELETE `/hoopi_staff/working-hours`

Delete linia godzin pracy

Usuwa linię dostępności.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Linia godzin pracy deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/hoopi_staff/working-hours" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/hoopi_staff/working-hours`

List godziny pracy

Zwraca stronicowaną listę: godziny pracy. Zakres najemcy i organizacji pochodzi z kontekstu żądania.

**Tags:** Prowadzący

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| sortDir | query | any | Optional |
| id | query | any | Optional |
| profileId | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated godziny pracy

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "profile_id": null,
      "weekday": null,
      "starts_at": null,
      "ends_at": null,
      "note": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/hoopi_staff/working-hours?page=1&pageSize=50&sortDir=asc" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/hoopi_staff/working-hours`

Create linia godzin pracy

Dodaje linię dostępności. Nakładające się linie są dozwolone świadomie; odrzucane są wyłącznie dzień tygodnia spoza 1–7 i koniec nie późniejszy niż początek.

**Tags:** Prowadzący

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "profileId": "00000000-0000-4000-8000-000000000000",
  "weekday": 1,
  "startsAt": "string",
  "endsAt": "string"
}
```

### Responses

**201** – Linia godzin pracy created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/hoopi_staff/working-hours" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"profileId\": \"00000000-0000-4000-8000-000000000000\",
  \"weekday\": 1,
  \"startsAt\": \"string\",
  \"endsAt\": \"string\"
}"
```

## PUT `/hoopi_staff/working-hours`

Update linia godzin pracy

Nadpisuje linię. Wersji historycznej nie ma — siatka opisuje stan bieżący.

**Tags:** Prowadzący

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "updatedAt": "string",
  "note": null
}
```

### Responses

**200** – Linia godzin pracy updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/hoopi_staff/working-hours" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"updatedAt\": \"string\",
  \"note\": null
}"
```

## GET `/notifications`

List notifications

Returns a paginated collection of notifications.

**Tags:** Notifications

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| status | query | any | Optional |
| type | query | any | Optional |
| severity | query | any | Optional |
| sourceEntityType | query | any | Optional |
| sourceEntityId | query | any | Optional |
| since | query | any | Optional |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated notifications

Content-Type: `application/json`

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

**403** – Request could not be resolved to a tenant scope (code: tenant_scope_required)

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/notifications?page=1&pageSize=20" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/notifications`

Create notification

Creates a notification for a user.

**Tags:** Notifications

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**201** – Notification created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://demo.hoopi.io/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`

POST /notifications/{id}/action

**Tags:** Notifications

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/notifications/:id/action" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/notifications/{id}/dismiss`

PUT /notifications/{id}/dismiss

**Tags:** Notifications

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/notifications/:id/dismiss" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/notifications/{id}/read`

PUT /notifications/{id}/read

**Tags:** Notifications

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/notifications/:id/read" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/notifications/{id}/restore`

PUT /notifications/{id}/restore

**Tags:** Notifications

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/notifications/:id/restore" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/notifications/admin/preferences`

GET /notifications/admin/preferences

**Tags:** Notifications

**Requires authentication.**

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/notifications/admin/preferences" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/notifications/admin/preferences`

PUT /notifications/admin/preferences

**Tags:** Notifications

**Requires authentication.**

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/notifications/admin/preferences" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/notifications/batch`

POST /notifications/batch

**Tags:** Notifications

**Requires authentication.**

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/notifications/batch" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/notifications/channels`

GET /notifications/channels

**Tags:** Notifications

**Requires authentication.**

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/notifications/channels" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/notifications/feature`

POST /notifications/feature

**Tags:** Notifications

**Requires authentication.**

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/notifications/feature" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/notifications/mark-all-read`

PUT /notifications/mark-all-read

**Tags:** Notifications

**Requires authentication.**

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/notifications/mark-all-read" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/notifications/preferences`

GET /notifications/preferences

**Tags:** Notifications

**Requires authentication.**

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/notifications/preferences" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/notifications/preferences`

PUT /notifications/preferences

**Tags:** Notifications

**Requires authentication.**

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/notifications/preferences" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/notifications/role`

POST /notifications/role

**Tags:** Notifications

**Requires authentication.**

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/notifications/role" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/notifications/settings`

GET /notifications/settings

**Tags:** Notifications

**Requires authentication.**

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/notifications/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/notifications/settings`

POST /notifications/settings

**Tags:** Notifications

**Requires authentication.**

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/notifications/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/notifications/types`

GET /notifications/types

**Tags:** Notifications

**Requires authentication.**

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/notifications/types" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/notifications/types`

PATCH /notifications/types

**Tags:** Notifications

**Requires authentication.**

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/api/notifications/types" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/notifications/types/{id}/channels/{channel}`

DELETE /notifications/types/{id}/channels/{channel}

**Tags:** Notifications

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| channel | path | any | Required |

### Responses

**204** – Success

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/notifications/types/:id/channels/:channel" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/notifications/types/{id}/channels/{channel}`

PUT /notifications/types/{id}/channels/{channel}

**Tags:** Notifications

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| channel | path | any | Required |

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/notifications/types/:id/channels/:channel" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/notifications/unread-count`

GET /notifications/unread-count

**Tags:** Notifications

**Requires authentication.**

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/notifications/unread-count" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/perspectives/{tableId}`

Load perspectives for a table

Returns personal perspectives and available role defaults for the requested table identifier.

**Tags:** Perspectives

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| tableId | path | any | Required |

### Responses

**200** – Current perspectives and defaults.

Content-Type: `application/json`

```json
{
  "tableId": "string",
  "perspectives": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "tableId": "string",
      "settings": {},
      "isDefault": true,
      "createdAt": "string",
      "updatedAt": null
    }
  ],
  "defaultPerspectiveId": null,
  "rolePerspectives": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "tableId": "string",
      "settings": {},
      "isDefault": true,
      "createdAt": "string",
      "updatedAt": null,
      "roleId": "00000000-0000-4000-8000-000000000000",
      "tenantId": null,
      "organizationId": null,
      "roleName": null
    }
  ],
  "manageableRolePerspectives": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "tableId": "string",
      "settings": {},
      "isDefault": true,
      "createdAt": "string",
      "updatedAt": null,
      "roleId": "00000000-0000-4000-8000-000000000000",
      "tenantId": null,
      "organizationId": null,
      "roleName": null
    }
  ],
  "roles": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "hasPerspective": true,
      "hasDefault": true
    }
  ],
  "canApplyToRoles": true
}
```

**400** – Invalid table identifier

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/perspectives/string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/perspectives/{tableId}`

Create or update a perspective

Saves a personal perspective and optionally applies the same configuration to selected roles.

**Tags:** Perspectives

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| tableId | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "settings": {}
}
```

### Responses

**200** – Perspective saved successfully.

Content-Type: `application/json`

```json
{
  "perspective": {
    "id": "00000000-0000-4000-8000-000000000000",
    "name": "string",
    "tableId": "string",
    "settings": {},
    "isDefault": true,
    "createdAt": "string",
    "updatedAt": null
  },
  "rolePerspectives": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "tableId": "string",
      "settings": {},
      "isDefault": true,
      "createdAt": "string",
      "updatedAt": null,
      "roleId": "00000000-0000-4000-8000-000000000000",
      "tenantId": null,
      "organizationId": null,
      "roleName": null
    }
  ],
  "clearedRoleIds": [
    "00000000-0000-4000-8000-000000000000"
  ]
}
```

**400** – Validation failed or invalid roles provided

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing perspectives.role_defaults feature for role updates

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Optimistic lock conflict or perspective name already exists

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/perspectives/string" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"settings\": {}
}"
```

## DELETE `/perspectives/{tableId}/{perspectiveId}`

Delete a personal perspective

Removes a perspective owned by the current user for the given table.

**Tags:** Perspectives

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| tableId | path | any | Required |
| perspectiveId | path | any | Required |

### Responses

**200** – Perspective removed.

Content-Type: `application/json`

```json
{
  "success": true
}
```

**400** – Invalid identifiers supplied

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Perspective not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Optimistic lock conflict

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/perspectives/string/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/perspectives/{tableId}/roles/{roleId}`

Clear role perspectives for a table

Removes all role-level perspectives associated with the provided role identifier for the table.

**Tags:** Perspectives

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| tableId | path | any | Required |
| roleId | path | any | Required |

### Request Body

Content-Type: `application/json`

No example available for this content type.

### Responses

**200** – Role perspectives cleared.

Content-Type: `application/json`

```json
{
  "success": true
}
```

**400** – Invalid identifiers supplied

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing perspectives.role_defaults feature

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Role not found in scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Optimistic lock conflict

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/perspectives/string/roles/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/planner/availability`

Delete availability rule

Deletes an availability rule by id.

**Tags:** Planner

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Availability rule deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/planner/availability" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/planner/availability`

List availability rules

Returns a paginated collection of availability rules scoped to the authenticated organization.

**Tags:** Planner

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| subjectType | query | any | Optional |
| subjectIds | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated availability rules

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "organization_id": null,
      "tenant_id": null,
      "subject_type": null,
      "subject_id": null,
      "timezone": null,
      "rrule": null,
      "exdates": null,
      "kind": null,
      "note": null,
      "unavailability_reason_entry_id": null,
      "unavailability_reason_value": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/planner/availability?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/planner/availability`

Create availability rule

Creates an availability rule for the selected subject.

**Tags:** Planner

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "subjectType": "member",
  "subjectId": "00000000-0000-4000-8000-000000000000",
  "timezone": "string",
  "rrule": "string",
  "exdates": [],
  "kind": "availability",
  "note": null,
  "unavailabilityReasonEntryId": null,
  "unavailabilityReasonValue": null
}
```

### Responses

**201** – Availability rule created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/planner/availability" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"subjectType\": \"member\",
  \"subjectId\": \"00000000-0000-4000-8000-000000000000\",
  \"timezone\": \"string\",
  \"rrule\": \"string\",
  \"exdates\": [],
  \"kind\": \"availability\",
  \"note\": null,
  \"unavailabilityReasonEntryId\": null,
  \"unavailabilityReasonValue\": null
}"
```

## PUT `/planner/availability`

Update availability rule

Updates an availability rule by id.

**Tags:** Planner

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "note": null,
  "unavailabilityReasonEntryId": null,
  "unavailabilityReasonValue": null
}
```

### Responses

**200** – Availability rule updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/planner/availability" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"note\": null,
  \"unavailabilityReasonEntryId\": null,
  \"unavailabilityReasonValue\": null
}"
```

## POST `/planner/availability-date-specific`

Replace date-specific availability

Replaces date-specific availability rules for the subject in a single request.

**Tags:** Planner

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "subjectType": "member",
  "subjectId": "00000000-0000-4000-8000-000000000000",
  "timezone": "string",
  "windows": [],
  "isAvailable": true,
  "note": null,
  "unavailabilityReasonEntryId": null,
  "unavailabilityReasonValue": null
}
```

### Responses

**200** – Date-specific availability updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Conflict — the subject/date availability was modified by another edit

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "currentUpdatedAt": "string",
  "expectedUpdatedAt": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/planner/availability-date-specific" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"subjectType\": \"member\",
  \"subjectId\": \"00000000-0000-4000-8000-000000000000\",
  \"timezone\": \"string\",
  \"windows\": [],
  \"isAvailable\": true,
  \"note\": null,
  \"unavailabilityReasonEntryId\": null,
  \"unavailabilityReasonValue\": null
}"
```

## DELETE `/planner/availability-rule-sets`

Delete availability rule set

Deletes an availability rule set by id.

**Tags:** Planner

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Availability rule set deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/planner/availability-rule-sets" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/planner/availability-rule-sets`

List availability rule sets

Returns a paginated collection of availability rule sets scoped to the authenticated organization.

**Tags:** Planner

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |
| search | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |

### Responses

**200** – Paginated availability rule sets

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "organization_id": null,
      "tenant_id": null,
      "name": null,
      "description": null,
      "timezone": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/planner/availability-rule-sets?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/planner/availability-rule-sets`

Create availability rule set

Creates a reusable availability rule set.

**Tags:** Planner

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "description": null,
  "timezone": "string"
}
```

### Responses

**201** – Availability rule set created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/planner/availability-rule-sets" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\",
  \"description\": null,
  \"timezone\": \"string\"
}"
```

## PUT `/planner/availability-rule-sets`

Update availability rule set

Updates an availability rule set by id.

**Tags:** Planner

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "description": null
}
```

### Responses

**200** – Availability rule set updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/planner/availability-rule-sets" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"description\": null
}"
```

## POST `/planner/availability-weekly`

Replace weekly availability

Replaces weekly availability rules for the subject in a single request.

**Tags:** Planner

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "subjectType": "member",
  "subjectId": "00000000-0000-4000-8000-000000000000",
  "timezone": "string",
  "windows": []
}
```

### Responses

**200** – Weekly availability updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Optimistic lock conflict

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "optimistic_lock_conflict",
  "currentUpdatedAt": "string",
  "expectedUpdatedAt": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/planner/availability-weekly" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"subjectType\": \"member\",
  \"subjectId\": \"00000000-0000-4000-8000-000000000000\",
  \"timezone\": \"string\",
  \"windows\": []
}"
```

## POST `/query_index/purge`

Purge query index records

Queues a purge job to remove indexed records for an entity type within the active scope.

**Tags:** Query Index

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityType": "string"
}
```

### Responses

**200** – Purge job accepted.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Missing entity type

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/query_index/purge" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityType\": \"string\"
}"
```

## POST `/query_index/reindex`

Trigger query index rebuild

Queues a reindex job for the specified entity type within the current tenant scope.

**Tags:** Query Index

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entityType": "string"
}
```

### Responses

**200** – Reindex job accepted.

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**400** – Missing entity type

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/query_index/reindex" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entityType\": \"string\"
}"
```

## GET `/query_index/status`

Inspect query index coverage

Returns entity counts comparing base tables with the query index along with the latest job status.

**Tags:** Query Index

**Requires authentication.**

### Responses

**200** – Current query index status.

Content-Type: `application/json`

```json
{
  "items": [
    {
      "entityId": "string",
      "label": "string",
      "baseCount": null,
      "indexCount": null,
      "vectorCount": null,
      "fulltextCount": null,
      "ok": true,
      "job": {
        "status": "idle",
        "startedAt": null,
        "finishedAt": null,
        "heartbeatAt": null,
        "processedCount": null,
        "totalCount": null,
        "scope": null
      },
      "refreshedAt": 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"
    }
  ]
}
```

**400** – Tenant or organization context required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Authentication required

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/query_index/status" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/resources/activities`

Delete resourceactivity

Deletes a resource activity.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – ResourceActivity deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/resources/activities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/resources/activities`

List resourceactivitys

Returns a paginated collection of resourceactivitys scoped to the authenticated organization.

**Tags:** Resources

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| entityId | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated resourceactivitys

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "resource_id": null,
      "activity_type": null,
      "subject": null,
      "body": null,
      "occurred_at": null,
      "author_user_id": null,
      "appearance_icon": null,
      "appearance_color": null,
      "organization_id": null,
      "tenant_id": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/resources/activities?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/resources/activities`

Create resourceactivity

Adds an activity to a resource timeline.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000",
  "activityType": "string",
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**201** – ResourceActivity created

Content-Type: `application/json`

```json
{
  "id": null,
  "authorUserId": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/resources/activities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\",
  \"activityType\": \"string\",
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## PUT `/resources/activities`

Update resourceactivity

Updates a resource activity.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**200** – ResourceActivity updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/resources/activities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## DELETE `/resources/comments`

Delete resourcecomment

Deletes a resource note.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – ResourceComment deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/resources/comments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/resources/comments`

List resourcecomments

Returns a paginated collection of resourcecomments scoped to the authenticated organization.

**Tags:** Resources

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| entityId | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated resourcecomments

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "resource_id": null,
      "body": null,
      "author_user_id": null,
      "appearance_icon": null,
      "appearance_color": null,
      "organization_id": null,
      "tenant_id": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/resources/comments?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/resources/comments`

Create resourcecomment

Adds a note to a resource timeline.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000",
  "body": "string",
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**201** – ResourceComment created

Content-Type: `application/json`

```json
{
  "id": null,
  "authorUserId": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/resources/comments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\",
  \"body\": \"string\",
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## PUT `/resources/comments`

Update resourcecomment

Updates a resource note.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**200** – ResourceComment updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/resources/comments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## DELETE `/resources/resource-types`

Delete resource type

Deletes a resource type by id.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Resource type deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/resources/resource-types" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/resources/resource-types`

List resource types

Returns a paginated collection of resource types scoped to the authenticated organization.

**Tags:** Resources

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| withResourceCounts | query | any | Optional |

### Responses

**200** – Paginated resource types

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "organization_id": null,
      "tenant_id": null,
      "name": null,
      "description": null,
      "appearance_icon": null,
      "appearance_color": null,
      "created_at": null,
      "updated_at": null,
      "resourceCount": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/resources/resource-types?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/resources/resource-types`

Create resource type

Creates a resource type for resources resources.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "description": null,
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**201** – Resource type created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/resources/resource-types" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\",
  \"description\": null,
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## PUT `/resources/resource-types`

Update resource type

Updates a resource type by id.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "description": null,
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**200** – Resource type updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/resources/resource-types" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"description\": null,
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## DELETE `/resources/resources`

Delete resource

Deletes a resource by id.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Resource deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/resources/resources" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/resources/resources`

List resources

Returns a paginated collection of resources scoped to the authenticated organization.

**Tags:** Resources

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |
| resourceTypeId | query | any | Optional |
| isActive | query | any | Optional |
| tagIds | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |

### Responses

**200** – Paginated resources

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "organization_id": null,
      "tenant_id": null,
      "name": null,
      "description": null,
      "resource_type_id": null,
      "capacity": null,
      "capacity_unit_value": null,
      "capacity_unit_name": null,
      "capacity_unit_color": null,
      "capacity_unit_icon": null,
      "appearance_icon": null,
      "appearance_color": null,
      "is_active": null,
      "availability_rule_set_id": null,
      "custom_fieldset_code": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/resources/resources?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/resources/resources`

Create resource

Creates a resource scoped to the selected organization.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "description": null,
  "resourceTypeId": null,
  "capacity": null,
  "capacityUnitValue": null,
  "appearanceIcon": null,
  "appearanceColor": null,
  "availabilityRuleSetId": null,
  "customFieldsetCode": null
}
```

### Responses

**201** – Resource created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/resources/resources" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\",
  \"description\": null,
  \"resourceTypeId\": null,
  \"capacity\": null,
  \"capacityUnitValue\": null,
  \"appearanceIcon\": null,
  \"appearanceColor\": null,
  \"availabilityRuleSetId\": null,
  \"customFieldsetCode\": null
}"
```

## PUT `/resources/resources`

Update resource

Updates a resource by id.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "description": null,
  "resourceTypeId": null,
  "capacity": null,
  "capacityUnitValue": null,
  "appearanceIcon": null,
  "appearanceColor": null,
  "availabilityRuleSetId": null,
  "customFieldsetCode": null
}
```

### Responses

**200** – Resource updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/resources/resources" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"description\": null,
  \"resourceTypeId\": null,
  \"capacity\": null,
  \"capacityUnitValue\": null,
  \"appearanceIcon\": null,
  \"appearanceColor\": null,
  \"availabilityRuleSetId\": null,
  \"customFieldsetCode\": null
}"
```

## POST `/resources/resources/tags/assign`

Assign resource tag

Assigns a tag to a resources resource.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tagId": "00000000-0000-4000-8000-000000000000",
  "resourceId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**201** – Tag assignment created

Content-Type: `application/json`

```json
{
  "id": null
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/resources/resources/tags/assign" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tagId\": \"00000000-0000-4000-8000-000000000000\",
  \"resourceId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/resources/resources/tags/unassign`

Unassign resource tag

Removes a tag from a resources resource.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "tagId": "00000000-0000-4000-8000-000000000000",
  "resourceId": "00000000-0000-4000-8000-000000000000"
}
```

### Responses

**200** – Tag assignment removed

Content-Type: `application/json`

```json
{
  "id": null
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/resources/resources/tags/unassign" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"tagId\": \"00000000-0000-4000-8000-000000000000\",
  \"resourceId\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## DELETE `/resources/tags`

Delete resource tag

Deletes a resource tag by id.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Resource tag deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/resources/tags" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/resources/tags`

List resource tags

Returns a paginated collection of resource tags scoped to the authenticated organization.

**Tags:** Resources

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated resource tags

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "slug": null,
      "label": null,
      "color": null,
      "description": null,
      "organization_id": null,
      "tenant_id": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/resources/tags?page=1&pageSize=100" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/resources/tags`

Create resource tag

Creates a tag for resources resources and services.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "label": "string",
  "color": null,
  "description": null
}
```

### Responses

**201** – Resource tag created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/resources/tags" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"label\": \"string\",
  \"color\": null,
  \"description\": null
}"
```

## PUT `/resources/tags`

Update resource tag

Updates a resource tag by id.

**Tags:** Resources

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "color": null,
  "description": null
}
```

### Responses

**200** – Resource tag updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/resources/tags" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"color\": null,
  \"description\": null
}"
```

## DELETE `/staff/activities`

Delete teammemberactivity

Deletes a team member activity.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – TeamMemberActivity deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/staff/activities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/staff/activities`

List teammemberactivitys

Returns a paginated collection of teammemberactivitys scoped to the authenticated organization.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| entityId | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated teammemberactivitys

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "member_id": null,
      "activity_type": null,
      "subject": null,
      "body": null,
      "occurred_at": null,
      "author_user_id": null,
      "appearance_icon": null,
      "appearance_color": null,
      "organization_id": null,
      "tenant_id": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/activities?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/activities`

Create teammemberactivity

Adds an activity to a team member timeline.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000",
  "activityType": "string",
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**201** – TeamMemberActivity created

Content-Type: `application/json`

```json
{
  "id": null,
  "authorUserId": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/activities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\",
  \"activityType\": \"string\",
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## PUT `/staff/activities`

Update teammemberactivity

Updates a team member activity.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**200** – TeamMemberActivity updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/staff/activities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## DELETE `/staff/addresses`

Delete teammemberaddress

Deletes a team member address.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – TeamMemberAddress deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/staff/addresses" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/staff/addresses`

List teammemberaddresss

Returns a paginated collection of teammemberaddresss scoped to the authenticated organization.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| entityId | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated teammemberaddresss

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "member_id": null,
      "name": null,
      "purpose": null,
      "company_name": null,
      "address_line1": null,
      "address_line2": null,
      "building_number": null,
      "flat_number": null,
      "city": null,
      "region": null,
      "postal_code": null,
      "country": null,
      "latitude": null,
      "longitude": null,
      "is_primary": null,
      "organization_id": null,
      "tenant_id": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/addresses?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/addresses`

Create teammemberaddress

Adds a team member address.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000",
  "addressLine1": "string"
}
```

### Responses

**201** – TeamMemberAddress created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/addresses" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\",
  \"addressLine1\": \"string\"
}"
```

## PUT `/staff/addresses`

Update teammemberaddress

Updates a team member address.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – TeamMemberAddress updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/staff/addresses" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## DELETE `/staff/comments`

Delete teammembercomment

Deletes a team member note.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – TeamMemberComment deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/staff/comments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/staff/comments`

List teammembercomments

Returns a paginated collection of teammembercomments scoped to the authenticated organization.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| entityId | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated teammembercomments

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "member_id": null,
      "body": null,
      "author_user_id": null,
      "appearance_icon": null,
      "appearance_color": null,
      "organization_id": null,
      "tenant_id": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/comments?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/comments`

Create teammembercomment

Adds a note to a team member timeline.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000",
  "body": "string",
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**201** – TeamMemberComment created

Content-Type: `application/json`

```json
{
  "id": null,
  "authorUserId": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/comments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\",
  \"body\": \"string\",
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## PUT `/staff/comments`

Update teammembercomment

Updates a team member note.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**200** – TeamMemberComment updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/staff/comments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## DELETE `/staff/job-histories`

Delete teammemberjobhistory

Deletes a team member job history entry.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – TeamMemberJobHistory deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/staff/job-histories" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/staff/job-histories`

List teammemberjobhistorys

Returns a paginated collection of teammemberjobhistorys scoped to the authenticated organization.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| entityId | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated teammemberjobhistorys

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "member_id": null,
      "name": null,
      "company_name": null,
      "description": null,
      "start_date": null,
      "end_date": null,
      "organization_id": null,
      "tenant_id": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/job-histories?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/job-histories`

Create teammemberjobhistory

Adds a team member job history entry.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "entityId": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "companyName": null,
  "description": null,
  "startDate": "2025-01-01T00:00:00.000Z",
  "endDate": null
}
```

### Responses

**201** – TeamMemberJobHistory created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/job-histories" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"entityId\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\",
  \"companyName\": null,
  \"description\": null,
  \"startDate\": \"2025-01-01T00:00:00.000Z\",
  \"endDate\": null
}"
```

## PUT `/staff/job-histories`

Update teammemberjobhistory

Updates a team member job history entry.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "companyName": null,
  "description": null,
  "endDate": null
}
```

### Responses

**200** – TeamMemberJobHistory updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/staff/job-histories" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"companyName\": null,
  \"description\": null,
  \"endDate\": null
}"
```

## DELETE `/staff/leave-requests`

Delete leave request

Deletes a leave request by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Leave request deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/staff/leave-requests" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/staff/leave-requests`

List leave requests

Returns a paginated collection of leave requests scoped to the authenticated organization.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| status | query | any | Optional |
| memberId | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |

### Responses

**200** – Paginated leave requests

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "organization_id": null,
      "tenant_id": null,
      "member_id": null,
      "start_date": null,
      "end_date": null,
      "timezone": null,
      "status": null,
      "unavailability_reason_entry_id": null,
      "unavailability_reason_value": null,
      "note": null,
      "decision_comment": null,
      "submitted_by_user_id": null,
      "decided_by_user_id": null,
      "decided_at": null,
      "created_at": null,
      "updated_at": null,
      "member": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/leave-requests?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/leave-requests`

Create leave request

Creates a leave request for a staff member.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "memberId": "00000000-0000-4000-8000-000000000000",
  "timezone": "string",
  "startDate": "2025-01-01T00:00:00.000Z",
  "endDate": "2025-01-01T00:00:00.000Z",
  "unavailabilityReasonEntryId": null,
  "unavailabilityReasonValue": null,
  "note": null,
  "submittedByUserId": null
}
```

### Responses

**201** – Leave request created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/leave-requests" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"memberId\": \"00000000-0000-4000-8000-000000000000\",
  \"timezone\": \"string\",
  \"startDate\": \"2025-01-01T00:00:00.000Z\",
  \"endDate\": \"2025-01-01T00:00:00.000Z\",
  \"unavailabilityReasonEntryId\": null,
  \"unavailabilityReasonValue\": null,
  \"note\": null,
  \"submittedByUserId\": null
}"
```

## PUT `/staff/leave-requests`

Update leave request

Updates a leave request by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "unavailabilityReasonEntryId": null,
  "unavailabilityReasonValue": null,
  "note": null
}
```

### Responses

**200** – Leave request updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/staff/leave-requests" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"unavailabilityReasonEntryId\": null,
  \"unavailabilityReasonValue\": null,
  \"note\": null
}"
```

## POST `/staff/leave-requests/accept`

Approve leave request

Approves a leave request and adds unavailability rules for the staff member.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "decisionComment": null,
  "decidedByUserId": null
}
```

### Responses

**200** – Leave request approved

Content-Type: `application/json`

```json
{
  "ok": true,
  "id": null
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/leave-requests/accept" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"decisionComment\": null,
  \"decidedByUserId\": null
}"
```

## POST `/staff/leave-requests/reject`

Reject leave request

Rejects a leave request with an optional comment.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "decisionComment": null,
  "decidedByUserId": null
}
```

### Responses

**200** – Leave request rejected

Content-Type: `application/json`

```json
{
  "ok": true,
  "id": null
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/leave-requests/reject" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"decisionComment\": null,
  \"decidedByUserId\": null
}"
```

## DELETE `/staff/team-members`

Delete team member

Deletes a team member by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Team member deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/staff/team-members" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/staff/team-members`

List team members

Returns a paginated collection of team members scoped to the authenticated organization.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| isActive | query | any | Optional |
| teamId | query | any | Optional |
| roleId | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |

### Responses

**200** – Paginated team members

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "organization_id": null,
      "tenant_id": null,
      "team_id": null,
      "display_name": null,
      "description": null,
      "user_id": null,
      "availability_rule_set_id": null,
      "is_active": null,
      "created_at": null,
      "updated_at": null,
      "user": null,
      "team": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/team-members?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/team-members`

Create team member

Creates a team member for staff assignments.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "teamId": null,
  "displayName": "string",
  "description": null,
  "userId": null,
  "roleIds": [],
  "tags": [],
  "availabilityRuleSetId": null
}
```

### Responses

**201** – Team member created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/team-members" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"teamId\": null,
  \"displayName\": \"string\",
  \"description\": null,
  \"userId\": null,
  \"roleIds\": [],
  \"tags\": [],
  \"availabilityRuleSetId\": null
}"
```

## PUT `/staff/team-members`

Update team member

Updates a team member by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "teamId": null,
  "description": null,
  "userId": null,
  "availabilityRuleSetId": null
}
```

### Responses

**200** – Team member updated

Content-Type: `application/json`

```json
{
  "ok": true,
  "updatedAt": null
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/staff/team-members" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"teamId\": null,
  \"description\": null,
  \"userId\": null,
  \"availabilityRuleSetId\": null
}"
```

## GET `/staff/team-members/assignable`

List staff members that can be assigned from customer flows

Returns active staff members linked to auth users. Access requires either customers.roles.manage or customers.activities.manage. Owned by the staff module; consumed from customer flows via this canonical URL. Replaces the deprecated /api/customers/assignable-staff route.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |

### Responses

**200** – Assignable staff members

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "teamMemberId": "00000000-0000-4000-8000-000000000000",
      "userId": "00000000-0000-4000-8000-000000000000",
      "displayName": "string",
      "email": null,
      "teamName": null,
      "user": null,
      "team": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

**400** – Invalid request

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/team-members/assignable?page=1&pageSize=24" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/staff/team-members/self`

Get current user team member profile

Returns the staff team member linked to the current user, if any.

**Tags:** Staff

**Requires authentication.**

### Responses

**200** – Team member profile

Content-Type: `application/json`

```json
{
  "member": null
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/team-members/self" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/team-members/self`

Create current user team member profile

Creates a team member profile for the signed-in user.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "displayName": "string",
  "description": null
}
```

### Responses

**201** – Team member created

Content-Type: `application/json`

```json
{
  "id": null
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Already exists

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/team-members/self" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"displayName\": \"string\",
  \"description\": null
}"
```

## POST `/staff/team-members/tags/assign`

Assign team member tag

Assigns a tag to a staff team member.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "memberId": "00000000-0000-4000-8000-000000000000",
  "tag": "string"
}
```

### Responses

**201** – Tag assignment created

Content-Type: `application/json`

```json
{
  "id": null
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/team-members/tags/assign" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"memberId\": \"00000000-0000-4000-8000-000000000000\",
  \"tag\": \"string\"
}"
```

## POST `/staff/team-members/tags/unassign`

Unassign team member tag

Removes a tag from a staff team member.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "memberId": "00000000-0000-4000-8000-000000000000",
  "tag": "string"
}
```

### Responses

**200** – Tag assignment removed

Content-Type: `application/json`

```json
{
  "id": null
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/team-members/tags/unassign" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"memberId\": \"00000000-0000-4000-8000-000000000000\",
  \"tag\": \"string\"
}"
```

## DELETE `/staff/team-roles`

Delete team role

Deletes a team role by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Team role deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/staff/team-roles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/staff/team-roles`

List team roles

Returns a paginated collection of team roles scoped to the authenticated organization.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |
| teamId | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |

### Responses

**200** – Paginated team roles

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "organization_id": null,
      "tenant_id": null,
      "team_id": null,
      "name": null,
      "description": null,
      "appearance_icon": null,
      "appearance_color": null,
      "created_at": null,
      "updated_at": null,
      "team": null,
      "memberCount": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/team-roles?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/team-roles`

Create team role

Creates a team role for staff team members.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "teamId": null,
  "name": "string",
  "description": null,
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**201** – Team role created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/team-roles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"teamId\": null,
  \"name\": \"string\",
  \"description\": null,
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## PUT `/staff/team-roles`

Update team role

Updates a team role by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "teamId": null,
  "description": null,
  "appearanceIcon": null,
  "appearanceColor": null
}
```

### Responses

**200** – Team role updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/staff/team-roles" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"teamId\": null,
  \"description\": null,
  \"appearanceIcon\": null,
  \"appearanceColor\": null
}"
```

## DELETE `/staff/teams`

Delete team

Deletes a staff team by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Team deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/staff/teams" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/staff/teams`

List teams

Returns a paginated collection of teams scoped to the authenticated organization.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| search | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |
| isActive | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |

### Responses

**200** – Paginated teams

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "organization_id": null,
      "tenant_id": null,
      "name": null,
      "description": null,
      "is_active": null,
      "created_at": null,
      "updated_at": null,
      "memberCount": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/teams?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/teams`

Create team

Creates a staff team.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "description": null
}
```

### Responses

**201** – Team created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/teams" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\",
  \"description\": null
}"
```

## PUT `/staff/teams`

Update team

Updates a staff team by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "description": null
}
```

### Responses

**200** – Team updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/staff/teams" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"description\": null
}"
```

## GET `/staff/timesheets/my-projects`

List assigned time project memberships for the current user

Returns staff_time_project_members where the authenticated user is an active member. Used by the My Timesheets grid to resolve assigned project IDs (spec N+1 mitigation query 1).

**Tags:** Staff

**Requires authentication.**

### Responses

**200** – Assigned project memberships

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "time_project_id": "00000000-0000-4000-8000-000000000000",
      "staff_member_id": "00000000-0000-4000-8000-000000000000",
      "role": null,
      "status": null,
      "assigned_start_date": null,
      "assigned_end_date": null,
      "show_in_grid": true
    }
  ],
  "total": 1
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – No staff member linked

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/timesheets/my-projects" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/staff/timesheets/my-projects/{projectId}`

Toggle grid visibility for one of the caller's assigned time projects

Self-service endpoint. Only the authenticated user can toggle `show_in_grid` on their own active membership for the given project. Does not require the admin-only `staff.timesheets.projects.manage` feature.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| projectId | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "showInGrid": true
}
```

### Responses

**200** – Visibility updated

Content-Type: `application/json`

```json
{
  "ok": true,
  "showInGrid": true
}
```

**400** – Invalid input

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – No staff member linked

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Project membership not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/api/staff/timesheets/my-projects/:projectId" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"showInGrid\": true
}"
```

## GET `/staff/timesheets/projects/kpis`

Aggregate KPIs for the timesheets Projects page

Returns a role-aware KPI payload. Users with `staff.timesheets.projects.manage` get the PM shape (portfolio totals, monthly team hours, active team member count). Other viewers get the Collaborator shape scoped to their own memberships and hours.

**Tags:** Staff

**Requires authentication.**

### Responses

**200** – PM or Collaborator KPIs

Content-Type: `application/json`

```json
{
  "role": "pm",
  "totals": {
    "total": 1,
    "active": 1,
    "onHold": 1,
    "completed": 1
  },
  "hoursWeek": {
    "current": 1,
    "previous": 1,
    "deltaPct": null
  },
  "hoursMonth": {
    "current": 1,
    "previous": 1,
    "deltaPct": null
  },
  "teamActive": {
    "count": 1
  },
  "assignedToMe": {
    "total": 1,
    "active": 1
  }
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Missing viewing feature or no staff member linked

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**500** – Aggregation failure

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/timesheets/projects/kpis" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/staff/timesheets/time-entries`

Delete timeentry

Deletes a time entry by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – TimeEntry deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/staff/timesheets/time-entries" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/staff/timesheets/time-entries`

List timeentries

Returns a paginated collection of timeentries scoped to the authenticated organization.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| staffMemberId | query | any | Optional |
| from | query | any | Optional |
| to | query | any | Optional |
| projectId | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |
| running | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |

### Responses

**200** – Paginated timeentries

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "organization_id": null,
      "tenant_id": null,
      "staff_member_id": null,
      "date": null,
      "duration_minutes": null,
      "started_at": null,
      "ended_at": null,
      "notes": null,
      "time_project_id": null,
      "customer_id": null,
      "deal_id": null,
      "order_id": null,
      "source": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/timesheets/time-entries?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/timesheets/time-entries`

Create timeentry

Creates a time entry for a staff member.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "staffMemberId": "00000000-0000-4000-8000-000000000000",
  "date": "2025-01-01T00:00:00.000Z",
  "durationMinutes": 1,
  "startedAt": null,
  "endedAt": null,
  "timeProjectId": null,
  "customerId": null,
  "dealId": null,
  "orderId": null,
  "notes": null,
  "source": "manual"
}
```

### Responses

**201** – TimeEntry created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/timesheets/time-entries" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"staffMemberId\": \"00000000-0000-4000-8000-000000000000\",
  \"date\": \"2025-01-01T00:00:00.000Z\",
  \"durationMinutes\": 1,
  \"startedAt\": null,
  \"endedAt\": null,
  \"timeProjectId\": null,
  \"customerId\": null,
  \"dealId\": null,
  \"orderId\": null,
  \"notes\": null,
  \"source\": \"manual\"
}"
```

## PUT `/staff/timesheets/time-entries`

Update timeentry

Updates a time entry by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "timeProjectId": null,
  "customerId": null,
  "dealId": null,
  "orderId": null,
  "notes": null
}
```

### Responses

**200** – TimeEntry updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/staff/timesheets/time-entries" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"timeProjectId\": null,
  \"customerId\": null,
  \"dealId\": null,
  \"orderId\": null,
  \"notes\": null
}"
```

## POST `/staff/timesheets/time-entries/{id}/segments`

Add a segment to a time entry

Creates a new work or break segment for the specified time entry.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "timeEntryId": "00000000-0000-4000-8000-000000000000",
  "startedAt": "2025-01-01T00:00:00.000Z",
  "endedAt": null,
  "segmentType": "work"
}
```

### Responses

**201** – Segment created

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "timeEntryId": "00000000-0000-4000-8000-000000000000",
  "startedAt": "string",
  "endedAt": null,
  "segmentType": "work",
  "createdAt": "string"
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Time entry not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/timesheets/time-entries/:id/segments" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"timeEntryId\": \"00000000-0000-4000-8000-000000000000\",
  \"startedAt\": \"2025-01-01T00:00:00.000Z\",
  \"endedAt\": null,
  \"segmentType\": \"work\"
}"
```

## PATCH `/staff/timesheets/time-entries/{id}/segments/{segmentId}`

Update a time entry segment

Updates fields on an existing time entry segment (startedAt, endedAt, segmentType).

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| segmentId | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "endedAt": null
}
```

### Responses

**200** – Segment updated successfully

Content-Type: `application/json`

```json
{
  "ok": true,
  "item": {
    "id": "string",
    "timeEntryId": "string",
    "startedAt": "string",
    "endedAt": null,
    "segmentType": "work",
    "createdAt": "string",
    "updatedAt": "string"
  }
}
```

**400** – Invalid payload or missing segment id

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Segment not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X PATCH "https://demo.hoopi.io/api/staff/timesheets/time-entries/:id/segments/:segmentId" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"endedAt\": null
}"
```

## POST `/staff/timesheets/time-entries/{id}/timer-start`

Start timer for a time entry

Starts the timer on a time entry by setting startedAt and creating an initial work segment.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Timer started

Content-Type: `application/json`

```json
{
  "ok": true
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Time entry not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Timer already started, or another timer is already running for this staff member

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/timesheets/time-entries/:id/timer-start" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/timesheets/time-entries/{id}/timer-stop`

Stop timer for a time entry

Stops the active timer segment, recalculates total work duration in minutes, and updates the time entry.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Responses

**200** – Timer stopped

Content-Type: `application/json`

```json
{
  "ok": true,
  "durationMinutes": 1
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**404** – Time entry not found

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – No active timer segment

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/timesheets/time-entries/:id/timer-stop" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/timesheets/time-entries/bulk`

Bulk save time entries

Creates, updates, or soft-deletes multiple time entries in a single request. Entries with durationMinutes=0 and an existing id are soft-deleted.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "entries": [
    {
      "id": null,
      "date": "2025-01-01T00:00:00.000Z",
      "timeProjectId": "00000000-0000-4000-8000-000000000000",
      "durationMinutes": 1,
      "notes": null
    }
  ]
}
```

### Responses

**200** – Bulk save completed

Content-Type: `application/json`

```json
{
  "ok": true,
  "created": 1,
  "updated": 1,
  "deleted": 1
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**422** – Validation error

Content-Type: `application/json`

```json
{
  "ok": false,
  "errors": [
    {
      "path": "string",
      "message": "string"
    }
  ]
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/timesheets/time-entries/bulk" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"entries\": [
    {
      \"id\": null,
      \"date\": \"2025-01-01T00:00:00.000Z\",
      \"timeProjectId\": \"00000000-0000-4000-8000-000000000000\",
      \"durationMinutes\": 1,
      \"notes\": null
    }
  ]
}"
```

## POST `/staff/timesheets/time-entries/start-timer`

Start a timesheet timer

Atomically creates a timer-sourced time entry and starts it (sets startedAt and creates the initial work segment) in a single transaction, so a partial failure cannot leave an orphaned, unstarted entry.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "staffMemberId": "00000000-0000-4000-8000-000000000000",
  "date": "2025-01-01T00:00:00.000Z",
  "timeProjectId": null,
  "notes": null
}
```

### Responses

**201** – Timer started

Content-Type: `application/json`

```json
{
  "ok": true,
  "id": null
}
```

**400** – Invalid payload

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**401** – Unauthorized

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**403** – Forbidden

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**409** – Another timer is already running for this staff member

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

**422** – Referenced time project not found or out of scope

Content-Type: `application/json`

```json
{
  "error": "string"
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/timesheets/time-entries/start-timer" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"staffMemberId\": \"00000000-0000-4000-8000-000000000000\",
  \"date\": \"2025-01-01T00:00:00.000Z\",
  \"timeProjectId\": null,
  \"notes\": null
}"
```

## DELETE `/staff/timesheets/time-projects`

Delete timeproject

Soft-deletes a time project by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – TimeProject deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/staff/timesheets/time-projects" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/staff/timesheets/time-projects`

List time projects

Returns a paginated collection of time projects scoped to the authenticated organization.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| q | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |
| projectType | query | any | Optional |
| status | query | any | Optional |
| customerId | query | any | Optional |
| mine | query | any | Optional |
| include | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |

### Responses

**200** – Paginated time projects

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "organization_id": null,
      "tenant_id": null,
      "name": null,
      "code": null,
      "description": null,
      "project_type": null,
      "color": null,
      "status": null,
      "customer_id": null,
      "owner_user_id": null,
      "cost_center": null,
      "start_date": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/timesheets/time-projects?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/timesheets/time-projects`

Create timeproject

Creates a time project.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "name": "string",
  "customerId": null,
  "code": "string",
  "description": null,
  "projectType": null,
  "color": null,
  "status": "active",
  "ownerUserId": null,
  "costCenter": null,
  "startDate": null
}
```

### Responses

**201** – TimeProject created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/timesheets/time-projects" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"name\": \"string\",
  \"customerId\": null,
  \"code\": \"string\",
  \"description\": null,
  \"projectType\": null,
  \"color\": null,
  \"status\": \"active\",
  \"ownerUserId\": null,
  \"costCenter\": null,
  \"startDate\": null
}"
```

## PUT `/staff/timesheets/time-projects`

Update timeproject

Updates a time project by id.

**Tags:** Staff

**Requires authentication.**

### Request Body

Content-Type: `application/json`

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "customerId": null,
  "description": null,
  "projectType": null,
  "color": null,
  "ownerUserId": null,
  "costCenter": null,
  "startDate": null
}
```

### Responses

**200** – TimeProject updated

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X PUT "https://demo.hoopi.io/api/staff/timesheets/time-projects" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\",
  \"customerId\": null,
  \"description\": null,
  \"projectType\": null,
  \"color\": null,
  \"ownerUserId\": null,
  \"costCenter\": null,
  \"startDate\": null
}"
```

## DELETE `/staff/timesheets/time-projects/{id}/employees`

Delete timeprojectmember

Unassigns an employee from a time project.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – TimeProjectMember deleted

Content-Type: `application/json`

```json
{
  "ok": true
}
```

### Example

```bash
curl -X DELETE "https://demo.hoopi.io/api/staff/timesheets/time-projects/:id/employees" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/staff/timesheets/time-projects/{id}/employees`

List time project members

Returns a paginated collection of time project members scoped to the authenticated organization.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| timeProjectId | query | any | Optional |
| status | query | any | Optional |
| sortField | query | any | Optional |
| sortDir | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated time project members

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": null,
      "organization_id": null,
      "tenant_id": null,
      "time_project_id": null,
      "staff_member_id": null,
      "role": null,
      "status": null,
      "assigned_start_date": null,
      "assigned_end_date": null,
      "created_at": null,
      "updated_at": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://demo.hoopi.io/api/staff/timesheets/time-projects/:id/employees?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/staff/timesheets/time-projects/{id}/employees`

Create timeprojectmember

Assigns an employee to a time project.

**Tags:** Staff

**Requires authentication.**

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | path | any | Required |

### Request Body

Content-Type: `application/json`

```json
{
  "tenantId": "00000000-0000-4000-8000-000000000000",
  "organizationId": "00000000-0000-4000-8000-000000000000",
  "timeProjectId": "00000000-0000-4000-8000-000000000000",
  "staffMemberId": "00000000-0000-4000-8000-000000000000",
  "role": null,
  "status": "active",
  "assignedStartDate": "2025-01-01T00:00:00.000Z",
  "assignedEndDate": null
}
```

### Responses

**201** – TimeProjectMember created

Content-Type: `application/json`

```json
{
  "id": null
}
```

### Example

```bash
curl -X POST "https://demo.hoopi.io/api/staff/timesheets/time-projects/:id/employees" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"tenantId\": \"00000000-0000-4000-8000-000000000000\",
  \"organizationId\": \"00000000-0000-4000-8000-000000000000\",
  \"timeProjectId\": \"00000000-0000-4000-8000-000000000000\",
  \"staffMemberId\": \"00000000-0000-4000-8000-000000000000\",
  \"role\": null,
  \"status\": \"active\",
  \"assignedStartDate\": \"2025-01-01T00:00:00.000Z\",
  \"assignedEndDate\": null
}"
```