# Open Mercato API

Version: 0.6.7

Auto-generated OpenAPI definition for all enabled modules.

## Servers
- https://open-book-partners-erp.northbound.run/api – Default environment

## GET `/advisory/documents`

Per-document ingest outcome, including which pages had no text layer and need OCR

Requires features: advisory.view

**Tags:** Advisory

**Requires authentication.**

**Features:** advisory.view

### Responses

**200** – Documents

Content-Type: `application/json`

```json
{
  "items": [
    {}
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/advisory/documents" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/advisory/draft`

Runs classify -> evidence -> draft inline and stores the result. Omit questionId to draft all pending questions.

Requires features: advisory.review

**Tags:** Advisory

**Requires authentication.**

**Features:** advisory.review

### Responses

**200** – Outcomes

Content-Type: `application/json`

```json
{
  "ok": true,
  "outcomes": [
    {}
  ]
}
```

**503** – Document search is not configured

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/advisory/draft" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/advisory/organizations`

GET /advisory/organizations

Requires features: advisory.view

**Tags:** Advisory Inbox

**Requires authentication.**

**Features:** advisory.view

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/advisory/organizations" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/advisory/questions`

Questions from every connected source with their latest cited draft reply, scoped to the selected company plus anything still unassigned

Requires features: advisory.view

**Tags:** Advisory

**Requires authentication.**

**Features:** advisory.view

### Responses

**200** – Inbox rows

Content-Type: `application/json`

```json
{
  "items": [
    {}
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/advisory/questions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/advisory/questions`

Advisory review inbox

Requires features: advisory.review

**Tags:** Advisory

**Requires authentication.**

**Features:** advisory.review

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/advisory/questions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/advisory/review`

Approve, save edits to, or dismiss a drafted reply. Records the reviewer and their text; sends nothing anywhere.

Requires features: advisory.review

**Tags:** Advisory

**Requires authentication.**

**Features:** advisory.review

### Responses

**200** – Updated

Content-Type: `application/json`

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

**409** – No draft to review yet

Content-Type: `application/json`

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

**422** – Refused — an approved reply must cite the document or figure it came from

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/advisory/review" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/api_keys/keys`

Delete API key

Removes an API key by identifier. The key must belong to the current tenant and fall within the requester organization scope.

Requires features: api_keys.delete

**Tags:** API Keys

**Requires authentication.**

**Features:** api_keys.delete

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Required. API key identifier to delete |

### Responses

**200** – Key deleted successfully

Content-Type: `application/json`

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

**400** – Missing or invalid identifier

Content-Type: `application/json`

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

**404** – Key not found within scope

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/api_keys/keys?id=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/api_keys/keys`

List API keys

Returns paginated API keys visible to the current user, including per-key role assignments and organization context.

Requires features: api_keys.view

**Tags:** API Keys

**Requires authentication.**

**Features:** api_keys.view

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

### Responses

**200** – Collection of API keys

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "string",
      "name": "string",
      "description": null,
      "keyPrefix": "string",
      "organizationId": null,
      "organizationName": null,
      "createdAt": "string",
      "lastUsedAt": null,
      "expiresAt": null,
      "roles": [
        {
          "id": "string",
          "name": null
        }
      ]
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1,
  "totalPages": 1
}
```

**400** – Tenant context missing

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/api_keys/keys" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/api_keys/keys`

Create API key

Creates a new API key, returning the one-time secret value together with the generated key prefix and scope details.

Requires features: api_keys.create

**Tags:** API Keys

**Requires authentication.**

**Features:** api_keys.create

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "description": null,
  "tenantId": null,
  "organizationId": null,
  "roles": [],
  "expiresAt": null
}
```

### Responses

**201** – API key created successfully

Content-Type: `application/json`

```json
{
  "id": "string",
  "name": "string",
  "keyPrefix": "string",
  "tenantId": null,
  "organizationId": null,
  "roles": [
    {
      "id": "string",
      "name": null
    }
  ]
}
```

**400** – Invalid payload or missing tenant context

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/api_keys/keys" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"description\": null,
  \"tenantId\": null,
  \"organizationId\": null,
  \"roles\": [],
  \"expiresAt\": null
}"
```

## DELETE `/attachments`

Delete attachment

Removes an uploaded attachment and deletes the stored asset.

Requires features: attachments.manage

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.manage

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

**404** – Attachment not found

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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.

Requires features: attachments.view

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.view

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: attachments.manage

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.manage

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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.

Requires features: attachments.view

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.view

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: attachments.manage

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.manage

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

**404** – Attachment not found

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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.

Requires features: attachments.view

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.view

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

**404** – Attachment not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: attachments.manage

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: attachments.manage

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: attachments.manage

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.manage

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: attachments.manage

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.manage

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

**409** – Partition code already exists

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: attachments.manage

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.manage

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

**404** – Partition not found

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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.

Requires features: attachments.manage

**Tags:** Attachments

**Requires authentication.**

**Features:** attachments.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: audit_logs.view_self

**Tags:** Audit & Action Logs

**Requires authentication.**

**Features:** audit_logs.view_self

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: audit_logs.view_self

**Tags:** Audit & Action Logs

**Requires authentication.**

**Features:** audit_logs.view_self

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: audit_logs.view_self

**Tags:** Audit & Action Logs

**Requires authentication.**

**Features:** audit_logs.view_self

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: audit_logs.redo_self

**Tags:** Audit & Action Logs

**Requires authentication.**

**Features:** audit_logs.redo_self

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: audit_logs.undo_self

**Tags:** Audit & Action Logs

**Requires authentication.**

**Features:** audit_logs.undo_self

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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"
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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"
}
```

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: auth.acl.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.acl.manage

### Responses

**200** – Aggregated feature catalog

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/api/auth/logout" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/auth/profile`

Get current profile

Returns the email address for the signed-in user.

**Tags:** Authentication & Accounts

**Requires authentication.**

### Responses

**200** – Profile payload

Content-Type: `application/json`

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

**404** – User not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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"
}
```

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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.

Requires features: auth.roles.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.roles.manage

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

**404** – Role not found

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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.

Requires features: auth.roles.list

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.roles.list

### 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://open-book-partners-erp.northbound.run/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.

Requires features: auth.roles.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.roles.manage

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: auth.roles.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.roles.manage

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

**404** – Role not found

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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.

Requires features: auth.acl.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.acl.manage

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

**404** – Role not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: auth.acl.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.acl.manage

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

**404** – Role not found

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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`.

Requires features: auth.sidebar.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.sidebar.manage

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

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

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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
}
```

**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://open-book-partners-erp.northbound.run/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.

Requires features: auth.sidebar.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.sidebar.manage

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

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

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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
    }
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: auth.sidebar.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.sidebar.manage

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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).

Requires features: auth.sidebar.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.sidebar.manage

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

### Responses

**200** – Variant deleted

Content-Type: `application/json`

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

**404** – Variant not found

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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
  }
}
```

**404** – Variant not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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).

Requires features: auth.sidebar.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.sidebar.manage

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

**404** – Variant not found

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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. Undo support is provided via the command bus.

Requires features: auth.users.delete

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.users.delete

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

Content-Type: `application/json`

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

**404** – User not found

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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).

Requires features: auth.users.list

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.users.list

### 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"
      ],
      "updatedAt": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: auth.users.create

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.users.create

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: auth.users.edit

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.users.edit

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

Content-Type: `application/json`

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

**404** – User not found

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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.

Requires features: auth.acl.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.acl.manage

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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

Configures per-user ACL overrides, including super admin access, feature list, and organization scope.

Requires features: auth.acl.manage

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.acl.manage

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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.

Requires features: auth.users.edit

**Tags:** Auth

**Requires authentication.**

**Features:** auth.users.edit

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

### Responses

**200** – Consent list returned

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: auth.users.create

**Tags:** Authentication & Accounts

**Requires authentication.**

**Features:** auth.users.create

### 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://open-book-partners-erp.northbound.run/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.

Requires features: configs.cache.view

**Tags:** Configs

**Requires authentication.**

**Features:** configs.cache.view

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: configs.cache.manage

**Tags:** Configs

**Requires authentication.**

**Features:** configs.cache.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: configs.manage

**Tags:** Configs

**Requires authentication.**

**Features:** configs.manage

### Responses

**200** – Module telemetry cleared

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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.

Requires features: configs.system_status.view

**Tags:** Configs

**Requires authentication.**

**Features:** configs.system_status.view

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: configs.system_status.view

**Tags:** Configs

**Requires authentication.**

**Features:** configs.system_status.view

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

**500** – Failed to load system status

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: configs.manage

**Tags:** Configs

**Requires authentication.**

**Features:** configs.manage

### Responses

**200** – Cache cleared successfully

Content-Type: `application/json`

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: configs.manage

**Tags:** Configs

**Requires authentication.**

**Features:** configs.manage

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

**500** – Failed to load upgrade actions

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: configs.manage

**Tags:** Configs

**Requires authentication.**

**Features:** configs.manage

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

**500** – Failed to execute upgrade action

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/configs/upgrade-actions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"actionId\": \"string\"
}"
```

## DELETE `/currencies/currencies`

Delete currency

Deletes a currency by id.

Requires features: currencies.manage

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.manage

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Currency deleted

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/currencies/currencies" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/currencies/currencies`

List currencies

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

Requires features: currencies.view

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.view

### 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 |
| isBase | query | any | Optional |
| isActive | query | any | Optional |
| code | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated currencies

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "string",
      "code": "string",
      "name": "string",
      "symbol": null,
      "decimalPlaces": 1,
      "thousandsSeparator": null,
      "decimalSeparator": null,
      "isBase": true,
      "isActive": true,
      "createdAt": null,
      "updatedAt": null,
      "organizationId": "string",
      "tenantId": "string"
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/currencies/currencies?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/currencies/currencies`

Create currency

Creates a new currency.

Requires features: currencies.manage

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.manage

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "string",
  "tenantId": "string",
  "code": "string",
  "name": "string",
  "symbol": null,
  "thousandsSeparator": null,
  "decimalSeparator": null
}
```

### Responses

**201** – Currency created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/currencies/currencies" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"string\",
  \"tenantId\": \"string\",
  \"code\": \"string\",
  \"name\": \"string\",
  \"symbol\": null,
  \"thousandsSeparator\": null,
  \"decimalSeparator\": null
}"
```

## PUT `/currencies/currencies`

Update currency

Updates an existing currency by id.

Requires features: currencies.manage

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.manage

### Request Body

Content-Type: `application/json`

```json
{
  "id": "string",
  "symbol": null,
  "thousandsSeparator": null,
  "decimalSeparator": null
}
```

### Responses

**200** – Currency updated

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/currencies/currencies" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"string\",
  \"symbol\": null,
  \"thousandsSeparator\": null,
  \"decimalSeparator\": null
}"
```

## GET `/currencies/currencies/options`

List currency options

Returns currencies formatted for select inputs.

Requires features: currencies.view

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.view

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| q | query | any | Optional |
| query | query | any | Optional |
| search | query | any | Optional |
| includeInactive | query | any | Optional |
| limit | query | any | Optional |

### Responses

**200** – Option list

Content-Type: `application/json`

```json
{
  "items": [
    {
      "value": "string",
      "label": "string"
    }
  ]
}
```

**400** – Invalid query

Content-Type: `application/json`

```json
{
  "items": []
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/currencies/currencies/options?limit=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/currencies/exchange-rates`

Delete exchangerate

Deletes an exchange rate by id.

Requires features: currencies.rates.manage

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.rates.manage

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – ExchangeRate deleted

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/currencies/exchange-rates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## GET `/currencies/exchange-rates`

List exchangerates

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

Requires features: currencies.rates.view

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.rates.view

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

### Responses

**200** – Paginated exchangerates

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "fromCurrencyCode": "string",
      "toCurrencyCode": "string",
      "rate": "string",
      "date": "string",
      "source": "string",
      "type": null,
      "isActive": true,
      "createdAt": null,
      "updatedAt": null,
      "organizationId": "00000000-0000-4000-8000-000000000000",
      "tenantId": "00000000-0000-4000-8000-000000000000"
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/currencies/exchange-rates?page=1&pageSize=50" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/currencies/exchange-rates`

Create exchangerate

Creates a new exchange rate.

Requires features: currencies.rates.manage

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.rates.manage

### Request Body

Content-Type: `application/json`

```json
{
  "organizationId": "string",
  "tenantId": "string",
  "fromCurrencyCode": "string",
  "toCurrencyCode": "string",
  "rate": "string",
  "type": null
}
```

### Responses

**201** – ExchangeRate created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/currencies/exchange-rates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"organizationId\": \"string\",
  \"tenantId\": \"string\",
  \"fromCurrencyCode\": \"string\",
  \"toCurrencyCode\": \"string\",
  \"rate\": \"string\",
  \"type\": null
}"
```

## PUT `/currencies/exchange-rates`

Update exchangerate

Updates an existing exchange rate by id.

Requires features: currencies.rates.manage

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.rates.manage

### Request Body

Content-Type: `application/json`

```json
{
  "id": "string",
  "type": null
}
```

### Responses

**200** – ExchangeRate updated

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/currencies/exchange-rates" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"string\",
  \"type\": null
}"
```

## DELETE `/currencies/fetch-configs`

Delete currency fetch configuration

Deletes a currency fetch configuration by id.

Requires features: currencies.fetch.manage

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.fetch.manage

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| id | query | any | Required. Currency fetch configuration identifier to delete |

### Responses

**200** – Currency fetch configuration deleted successfully

Content-Type: `application/json`

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

**400** – Bad request

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/currencies/fetch-configs?id=00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/currencies/fetch-configs`

List currency fetch configurations

Returns all currency fetch configurations scoped to the authenticated organization.

Requires features: currencies.fetch.view

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.fetch.view

### Responses

**200** – A list of currency fetch configurations

Content-Type: `application/json`

```json
{
  "configs": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "organizationId": "00000000-0000-4000-8000-000000000000",
      "tenantId": "00000000-0000-4000-8000-000000000000",
      "provider": "string",
      "isEnabled": true,
      "syncTime": null,
      "lastSyncAt": null,
      "lastSyncStatus": null,
      "lastSyncMessage": null,
      "lastSyncCount": null,
      "config": null,
      "createdAt": "string",
      "updatedAt": "string"
    }
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/currencies/fetch-configs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/currencies/fetch-configs`

Create currency fetch configuration

Creates a new currency fetch configuration.

Requires features: currencies.fetch.manage

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.fetch.manage

### Request Body

Content-Type: `application/json`

```json
{
  "provider": "NBP",
  "isEnabled": false,
  "syncTime": null,
  "config": null
}
```

### Responses

**201** – Currency fetch configuration created successfully

Content-Type: `application/json`

```json
{
  "config": {
    "id": "00000000-0000-4000-8000-000000000000",
    "organizationId": "00000000-0000-4000-8000-000000000000",
    "tenantId": "00000000-0000-4000-8000-000000000000",
    "provider": "string",
    "isEnabled": true,
    "syncTime": null,
    "lastSyncAt": null,
    "lastSyncStatus": null,
    "lastSyncMessage": null,
    "lastSyncCount": null,
    "config": null,
    "createdAt": "string",
    "updatedAt": "string"
  }
}
```

**400** – Bad request

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/currencies/fetch-configs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"provider\": \"NBP\",
  \"isEnabled\": false,
  \"syncTime\": null,
  \"config\": null
}"
```

## PUT `/currencies/fetch-configs`

Update currency fetch configuration

Updates an existing currency fetch configuration by id.

Requires features: currencies.fetch.manage

**Tags:** Currencies

**Requires authentication.**

**Features:** currencies.fetch.manage

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – Currency fetch configuration updated successfully

Content-Type: `application/json`

```json
{
  "config": {
    "id": "00000000-0000-4000-8000-000000000000",
    "organizationId": "00000000-0000-4000-8000-000000000000",
    "tenantId": "00000000-0000-4000-8000-000000000000",
    "provider": "string",
    "isEnabled": true,
    "syncTime": null,
    "lastSyncAt": null,
    "lastSyncStatus": null,
    "lastSyncMessage": null,
    "lastSyncCount": null,
    "config": null,
    "createdAt": "string",
    "updatedAt": "string"
  }
}
```

**400** – Bad request

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/currencies/fetch-configs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"syncTime\": null,
  \"config\": null,
  \"id\": \"00000000-0000-4000-8000-000000000000\"
}"
```

## POST `/currencies/fetch-rates`

Fetch currency rates

Fetches currency exchange rates from configured providers for a specific date.

**Tags:** Currencies

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – Currency rates fetched successfully

Content-Type: `application/json`

```json
{
  "totalFetched": 1,
  "byProvider": {
    "key": {
      "count": 1
    }
  },
  "errors": [
    "string"
  ]
}
```

**400** – Bad request

Content-Type: `application/json`

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

**401** – Unauthorized

Content-Type: `application/json`

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

**500** – Internal server error

Content-Type: `application/json`

```json
{
  "totalFetched": 1,
  "byProvider": {
    "key": {
      "count": 1
    }
  },
  "errors": [
    "string"
  ]
}
```

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/currencies/fetch-rates" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## GET `/dashboards/layout`

Load the current dashboard layout

Returns the saved widget layout together with the widgets the current user is allowed to place.

Requires features: dashboards.view

**Tags:** Dashboards

**Requires authentication.**

**Features:** dashboards.view

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: dashboards.configure

**Tags:** Dashboards

**Requires authentication.**

**Features:** dashboards.configure

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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.

Requires features: dashboards.configure

**Tags:** Dashboards

**Requires authentication.**

**Features:** dashboards.configure

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

**404** – Item not found

Content-Type: `application/json`

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

### Example

```bash
curl -X PATCH "https://open-book-partners-erp.northbound.run/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.

Requires features: dashboards.admin.assign-widgets

**Tags:** Dashboards

**Requires authentication.**

**Features:** dashboards.admin.assign-widgets

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: dashboards.admin.assign-widgets

**Tags:** Dashboards

**Requires authentication.**

**Features:** dashboards.admin.assign-widgets

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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.

Requires features: dashboards.admin.assign-widgets

**Tags:** Dashboards

**Requires authentication.**

**Features:** dashboards.admin.assign-widgets

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: dashboards.admin.assign-widgets

**Tags:** Dashboards

**Requires authentication.**

**Features:** dashboards.admin.assign-widgets

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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.

Requires features: dashboards.admin.assign-widgets

**Tags:** Dashboards

**Requires authentication.**

**Features:** dashboards.admin.assign-widgets

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: analytics.view

**Tags:** Dashboards

**Requires authentication.**

**Features:** analytics.view

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

**400** – Invalid request payload

Content-Type: `application/json`

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

**500** – Internal server error

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: analytics.view

**Tags:** Dashboards

**Requires authentication.**

**Features:** analytics.view

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

**400** – Invalid request payload

Content-Type: `application/json`

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

**500** – Internal server error

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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 `/data_sync/mappings`

List or create field mappings

Requires features: data_sync.configure

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.configure

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/data_sync/mappings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/data_sync/mappings`

List or create field mappings

Requires features: data_sync.configure

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.configure

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/data_sync/mappings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/data_sync/mappings/{id}`

Get, update, or delete a field mapping

Requires features: data_sync.configure

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.configure

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

### Responses

**204** – Success

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/data_sync/mappings/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/data_sync/mappings/{id}`

Get, update, or delete a field mapping

Requires features: data_sync.configure

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.configure

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/data_sync/mappings/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/data_sync/mappings/{id}`

Get, update, or delete a field mapping

Requires features: data_sync.configure

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.configure

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/data_sync/mappings/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/data_sync/options`

List data sync integration options

Requires features: data_sync.view

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.view

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/data_sync/options" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/data_sync/run`

Start a data sync run

Requires features: data_sync.run

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.run

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/data_sync/run" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/data_sync/runs`

List sync runs

Requires features: data_sync.view

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.view

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/data_sync/runs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/data_sync/runs/{id}`

Get sync run detail

Requires features: data_sync.view

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.view

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/data_sync/runs/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/data_sync/runs/{id}/cancel`

Cancel a running sync

Requires features: data_sync.run

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.run

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

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/data_sync/runs/:id/cancel" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/data_sync/runs/{id}/retry`

Retry a failed sync run

Requires features: data_sync.run

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.run

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

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/data_sync/runs/:id/retry" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/data_sync/schedules`

List or create sync schedules

Requires features: data_sync.configure

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.configure

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/data_sync/schedules" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/data_sync/schedules`

List or create sync schedules

Requires features: data_sync.configure

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.configure

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/data_sync/schedules" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/data_sync/schedules/{id}`

Manage a sync schedule

Requires features: data_sync.configure

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.configure

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

### Responses

**204** – Success

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/data_sync/schedules/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/data_sync/schedules/{id}`

Manage a sync schedule

Requires features: data_sync.configure

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.configure

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/data_sync/schedules/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/data_sync/schedules/{id}`

Manage a sync schedule

Requires features: data_sync.configure

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.configure

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/data_sync/schedules/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/data_sync/validate`

Validate sync connection

Requires features: data_sync.configure

**Tags:** Data Sync

**Requires authentication.**

**Features:** data_sync.configure

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/data_sync/validate" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/dictionaries`

List dictionaries

Returns dictionaries accessible to the current organization, optionally including inactive records.

Requires features: dictionaries.view

**Tags:** Dictionaries

**Requires authentication.**

**Features:** dictionaries.view

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

**500** – Failed to load dictionaries

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/dictionaries" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/dictionaries`

Create dictionary

Registers a dictionary scoped to the current organization.

Requires features: dictionaries.manage

**Tags:** Dictionaries

**Requires authentication.**

**Features:** dictionaries.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: dictionaries.manage

**Tags:** Dictionaries

**Requires authentication.**

**Features:** dictionaries.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: dictionaries.view

**Tags:** Dictionaries

**Requires authentication.**

**Features:** dictionaries.view

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: dictionaries.manage

**Tags:** Dictionaries

**Requires authentication.**

**Features:** dictionaries.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: dictionaries.view

**Tags:** Dictionaries

**Requires authentication.**

**Features:** dictionaries.view

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

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

**400** – Invalid parameters

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://open-book-partners-erp.northbound.run/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.

Requires features: dictionaries.manage

**Tags:** Dictionaries

**Requires authentication.**

**Features:** dictionaries.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: dictionaries.manage

**Tags:** Dictionaries

**Requires authentication.**

**Features:** dictionaries.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: dictionaries.manage

**Tags:** Dictionaries

**Requires authentication.**

**Features:** dictionaries.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: dictionaries.manage

**Tags:** Dictionaries

**Requires authentication.**

**Features:** dictionaries.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: dictionaries.manage

**Tags:** Dictionaries

**Requires authentication.**

**Features:** dictionaries.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: directory.organizations.view

**Tags:** Directory

**Requires authentication.**

**Features:** directory.organizations.view

### 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,
  "updatedAt": null
}
```

**400** – A concrete organization scope is required

Content-Type: `application/json`

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

**404** – Organization not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: directory.organizations.manage

**Tags:** Directory

**Requires authentication.**

**Features:** directory.organizations.manage

### 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,
  "updatedAt": null
}
```

**400** – Save failed

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://open-book-partners-erp.northbound.run/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
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/directory/organization-switcher" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/directory/organizations`

Delete organization

Soft deletes an organization identified by id.

Requires features: directory.organizations.manage

**Tags:** Directory

**Requires authentication.**

**Features:** directory.organizations.manage

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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.

Requires features: directory.organizations.view

**Tags:** Directory

**Requires authentication.**

**Features:** directory.organizations.view

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: directory.organizations.manage

**Tags:** Directory

**Requires authentication.**

**Features:** directory.organizations.manage

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: directory.organizations.manage

**Tags:** Directory

**Requires authentication.**

**Features:** directory.organizations.manage

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/api/directory/organizations/lookup" \
  -H "Accept: application/json"
```

## DELETE `/directory/tenants`

Delete tenant

Soft deletes the tenant identified by id.

Requires features: directory.tenants.manage

**Tags:** Directory

**Requires authentication.**

**Features:** directory.tenants.manage

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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.

Requires features: directory.tenants.view

**Tags:** Directory

**Requires authentication.**

**Features:** directory.tenants.view

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: directory.tenants.manage

**Tags:** Directory

**Requires authentication.**

**Features:** directory.tenants.manage

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: directory.tenants.manage

**Tags:** Directory

**Requires authentication.**

**Features:** directory.tenants.manage

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/api/directory/tenants/lookup" \
  -H "Accept: application/json"
```

## GET `/drive/connection`

Every Google Drive account this tenant has connected, and each one's current state

Requires features: drive.view

**Tags:** Drive

**Requires authentication.**

**Features:** drive.view

### Responses

**200** – { connections: [{ connectionId, provider, accountEmail, accountDisplayName, status, lastError, connectedAt, organizationId, organizations, disconnectable, retainsUnconfirmedGrant }] } — one entry per connected Google account whose client company is live; rows kept only as connect anchors (forgotten, never completed, or belonging to a deleted company) are omitted, so an empty array means nothing is connected rather than that no row exists

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/drive/connection" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/drive/connection/forget`

Clear the recorded address of a disconnected Drive account (local record only — revokes nothing)

Requires features: drive.connect

**Tags:** Drive

**Requires authentication.**

**Features:** drive.connect

### Responses

**200** – { forgotten: true }

Content-Type: `application/json`

**400** – No connectionId given

Content-Type: `application/json`

**409** – The connection is not disconnected, so there is nothing to forget yet

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/drive/connection/forget" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/drive/oauth/callback`

GET /drive/oauth/callback

**Tags:** Drive

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/drive/oauth/callback" \
  -H "Accept: application/json"
```

## POST `/drive/oauth/disconnect`

Disconnect ONE connected Google Drive account, named by connectionId (revoke at Google unless Gmail shares the grant, then forget it here)

Requires features: drive.connect

**Tags:** Drive

**Requires authentication.**

**Features:** drive.connect

### Responses

**200** – { disconnected: true, revoke } — revoke is revoked | shared | already_ended | nothing_to_revoke | failed

Content-Type: `application/json`

**400** – No connectionId given

Content-Type: `application/json`

**500** – Could not disconnect

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/drive/oauth/disconnect" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/drive/oauth/initiate`

POST /drive/oauth/initiate

Requires features: drive.connect

**Tags:** Drive

**Requires authentication.**

**Features:** drive.connect

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/drive/oauth/initiate" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/drive/scopes`

GET /drive/scopes

Requires features: drive.view

**Tags:** Drive

**Requires authentication.**

**Features:** drive.view

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/drive/scopes" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/drive/scopes`

PUT /drive/scopes

Requires features: drive.connect

**Tags:** Drive

**Requires authentication.**

**Features:** drive.connect

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/drive/scopes" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/drive/targets`

GET /drive/targets

Requires features: drive.view

**Tags:** Drive

**Requires authentication.**

**Features:** drive.view

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/drive/targets" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/entities/definitions`

Soft delete custom field definition

Marks the specified definition inactive and tombstones it for the current scope.

Requires features: entities.definitions.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.manage

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

**404** – Definition not found

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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"
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.definitions.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.manage

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.definitions.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.manage

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.definitions.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.manage

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.definitions.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.manage

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

**404** – Definition not found

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.definitions.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.manage

### 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://open-book-partners-erp.northbound.run/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.

Requires features: entities.definitions.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.manage

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.definitions.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.manage

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

**404** – Entity not found in scope

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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
    }
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.definitions.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.manage

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.definitions.view

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.view

### Responses

**200** – Current settings

Content-Type: `application/json`

```json
{
  "newEntitiesRestrictedByDefault": true
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.definitions.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.manage

### Request Body

Content-Type: `application/json`

```json
{
  "newEntitiesRestrictedByDefault": true
}
```

### Responses

**200** – Updated settings

Content-Type: `application/json`

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

**400** – Invalid payload

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.records.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.records.manage

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: entities.records.view

**Tags:** Entities

**Requires authentication.**

**Features:** entities.records.view

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.records.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.records.manage

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.records.manage

**Tags:** Entities

**Requires authentication.**

**Features:** entities.records.manage

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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.

Requires features: entities.definitions.view

**Tags:** Entities

**Requires authentication.**

**Features:** entities.definitions.view

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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"
    }
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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).

Requires features: workflows.view

**Tags:** Events

**Requires authentication.**

**Features:** workflows.view

### Responses

**200** – Declared events

Content-Type: `application/json`

```json
{
  "data": [
    {
      "id": "string",
      "label": "string"
    }
  ],
  "total": 1
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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"
}
```

**404** – Tenant not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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"
}
```

**404** – Tenant not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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"
}
```

**404** – Tenant not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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"
}
```

**404** – Tenant not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: feature_toggles.global.manage

**Tags:** Feature Toggles

**Requires authentication.**

**Features:** feature_toggles.global.manage

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

**404** – Feature toggle not found

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/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.

Requires features: feature_toggles.view

**Tags:** Feature Toggles

**Requires authentication.**

**Features:** feature_toggles.view

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: feature_toggles.global.manage

**Tags:** Feature Toggles

**Requires authentication.**

**Features:** feature_toggles.global.manage

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: feature_toggles.global.manage

**Tags:** Feature Toggles

**Requires authentication.**

**Features:** feature_toggles.global.manage

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

**404** – Feature toggle not found

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/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.

Requires features: feature_toggles.view

**Tags:** Feature Toggles

**Requires authentication.**

**Features:** feature_toggles.view

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

**404** – Feature toggle not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: feature_toggles.view

**Tags:** Feature Toggles

**Requires authentication.**

**Features:** feature_toggles.view

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

**404** – Feature toggle not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: feature_toggles.view

**Tags:** Feature Toggles

**Requires authentication.**

**Features:** feature_toggles.view

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: feature_toggles.manage

**Tags:** Feature Toggles

**Requires authentication.**

**Features:** feature_toggles.manage

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

**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://open-book-partners-erp.northbound.run/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
}"
```

## DELETE `/gusto/department-rules`

Forget the mapping for ONE Gusto department title, leaving every other rule as it is

The body is `{ "gustoDepartmentTitle": "…" }`, matched trimmed and case-folded. A row that is already gone answers 200 `{ "deleted": false }` when no version was named. A change of an existing rule names the `updatedAt` it was read with in the `x-om-ext-optimistic-lock-expected-updated-at` header (never in the body): without it the answer is 428 (`optimistic_lock_required`), and against a stale one it is 409 (`optimistic_lock_conflict`). Send the organization the page shows in `x-obp-organization`. Requires `gusto.manage`.

Requires features: gusto.manage

**Tags:** Gusto

**Requires authentication.**

**Features:** gusto.manage

### Request Body

Content-Type: `application/json`

```json
{
  "gustoDepartmentTitle": "string"
}
```

### Responses

**200** – Whether a rule was removed

Content-Type: `application/json`

```json
{
  "deleted": true
}
```

**400** – No organization selected, none stated by the page (`organization_not_stated`), a missing/blank title (`invalid_department_rule`), or no month-end department profile (`department_profile_missing`)

Content-Type: `application/json`

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

**409** – Deleted against a stale version, or a version named for a row that is gone (`optimistic_lock_conflict`), or the selected organization is no longer the one the page shows

Content-Type: `application/json`

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

**428** – An existing rule was deleted without the `x-om-ext-optimistic-lock-expected-updated-at` version header (`optimistic_lock_required`)

Content-Type: `application/json`

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

**500** – Unexpected failure, or a stored profile that could not be read back

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/gusto/department-rules" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"gustoDepartmentTitle\": \"string\"
}"
```

## PUT `/gusto/department-rules`

Map ONE Gusto department title to a month-end profile department, with capitalize and ramp

The body is ONE rule: `{ "gustoDepartmentTitle": "…", "profileDepartmentName": "…" | null,` "capitalizePct": 0..1 | null, "ramp": "…" | null }`. The title is trimmed and `""` is the default row; the profile department is stored in the month-end profile’s own spelling, and a name the profile does not hold is refused 400 `department_not_in_profile`. A held department title that differs from this one only by case is refused 409 `department_title_conflict`. A list is refused: the CSV importer owns bulk changes. A change of an existing rule names the `updatedAt` it was read with in the `x-om-ext-optimistic-lock-expected-updated-at` header (never in the body): without it the answer is 428 (`optimistic_lock_required`), and against a stale one it is 409 (`optimistic_lock_conflict`). Send the organization the page shows in `x-obp-organization`. Requires `gusto.manage`.

Requires features: gusto.manage

**Tags:** Gusto

**Requires authentication.**

**Features:** gusto.manage

### Request Body

Content-Type: `application/json`

```json
{
  "gustoDepartmentTitle": "string",
  "capitalizePct": null
}
```

### Responses

**200** – The saved rule (canonical profile spelling) and its new `updatedAt`

Content-Type: `application/json`

```json
{
  "rule": {
    "id": "string",
    "gustoDepartmentTitle": "string",
    "profileDepartmentName": null,
    "capitalizePct": null,
    "ramp": null,
    "updatedAt": "string"
  },
  "updatedAt": "string"
}
```

**400** – No organization selected, none stated by the page (`organization_not_stated`), an invalid rule (`invalid_department_rule`), no month-end department profile / an empty one (`department_profile_missing`), or a profile name the month-end profile does not hold (`department_not_in_profile`)

Content-Type: `application/json`

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

**409** – Saved against a stale version (`optimistic_lock_conflict`), a held title differing only by case (`department_title_conflict`), or the selected organization is no longer the one the page shows (`organization_changed`)

Content-Type: `application/json`

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

**428** – An existing rule was saved without the `x-om-ext-optimistic-lock-expected-updated-at` version header (`optimistic_lock_required`)

Content-Type: `application/json`

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

**500** – Unexpected failure, or a stored profile that could not be read back

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/gusto/department-rules" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"gustoDepartmentTitle\": \"string\",
  \"capitalizePct\": null
}"
```

## GET `/gusto/oauth/callback`

Gusto redirects here after the admin consents; the app exchanges the code and connects.

**Tags:** Gusto

### Responses

**200** – Success response

Content-Type: `application/json`

**302** – Redirect back to the integration page with a flash result

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/gusto/oauth/callback" \
  -H "Accept: application/json"
```

## POST `/gusto/oauth/initiate`

Discover the Gusto authorization server, register this deployment (DCR), mint a state nonce and return the authorize URL.

Requires features: gusto.connect

**Tags:** Gusto

**Requires authentication.**

**Features:** gusto.connect

### Responses

**200** – Authorize URL returned

Content-Type: `application/json`

```json
{
  "authorizeUrl": "string"
}
```

**400** – No concrete organization selected, none stated in `x-obp-organization`, or "All organizations" selected

Content-Type: `application/json`

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

**409** – The stated organization is not the one now selected, or the credentials store is unavailable

Content-Type: `application/json`

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

**502** – OAuth discovery or registration failed, or the authorization server is not the one this connection is pinned to

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/gusto/oauth/initiate" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/gusto/people/{personUuid}/override`

Reset a person to the department rules, against the version it was read at

The person uuid is the path parameter; there is no body. Send the version read in the `x-om-ext-optimistic-lock-expected-updated-at` header. A person with no exception is `{ "deleted": false }` when no version was sent, so a repeated reset is an answer rather than an error; a version naming a row that has gone is the gone-record 409. Send the organization the page shows in `x-obp-organization`. Requires `gusto.manage` and a selected organization; a mapped Gusto company is not required, so an override left behind by a moved company mapping can still be reset.

Requires features: gusto.manage

**Tags:** Gusto

**Requires authentication.**

**Features:** gusto.manage

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| personUuid | path | any | Required |
| x-om-ext-optimistic-lock-expected-updated-at | header | any | Optional |
| x-obp-organization | header | any | Optional |

### Responses

**200** – Whether an exception row was removed

Content-Type: `application/json`

```json
{
  "deleted": true
}
```

**400** – No organization selected (`organization_not_selected`), or none stated by the page (`organization_not_stated`)

Content-Type: `application/json`

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

**409** – The version named in `x-om-ext-optimistic-lock-expected-updated-at` is stale, or names a row that has gone (`optimistic_lock_conflict`); or the stated organization is not the one now selected (`organization_changed`)

Content-Type: `application/json`

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

**428** – An existing row was edited or deleted without the version it was read at (`optimistic_lock_required`)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/gusto/people/string/override" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/gusto/people/{personUuid}/override`

Save a person’s full exception set, against the version it was read at

The body is one full replacement — `{ "kind": "employee" | "contractor", "departmentTitle": string | null, "capitalizePct": 0..1 | null, "ramp": string | null, "include": boolean }`; a partial body is refused, and `capitalizePct` is a FRACTION (the UI converts a percent). `departmentTitle` is the Gusto department TITLE the person is counted with, and must name a department this organization can resolve. A body that states no exception at all removes the row and answers `{ override: null, updatedAt: null }`. Send the version read in the `x-om-ext-optimistic-lock-expected-updated-at` header — never in the body — and the organization the page shows in `x-obp-organization`. Requires `gusto.manage` and a selected, mapped organization.

Requires features: gusto.manage

**Tags:** Gusto

**Requires authentication.**

**Features:** gusto.manage

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| personUuid | path | any | Required |
| x-om-ext-optimistic-lock-expected-updated-at | header | any | Optional |
| x-obp-organization | header | any | Optional |

### Request Body

Content-Type: `application/json`

```json
{
  "kind": "employee",
  "departmentTitle": null,
  "capitalizePct": null,
  "ramp": null,
  "include": true
}
```

### Responses

**200** – The saved exception and its new `updatedAt`, or both null when the body reset the row

Content-Type: `application/json`

```json
{
  "override": null,
  "updatedAt": null
}
```

**400** – No organization selected (`organization_not_selected`), none stated by the page (`organization_not_stated`), a body the schema refuses (`invalid_override_body`), or a department no rule, department or synced person names (`department_unknown`)

Content-Type: `application/json`

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

**404** – The uuid is not in the synced roster of the mapped company (`person_not_found`)

Content-Type: `application/json`

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

**409** – The version named in `x-om-ext-optimistic-lock-expected-updated-at` is stale, or names a row that has gone (`optimistic_lock_conflict`); or the stated organization is not the one now selected (`organization_changed`); or no Gusto company is mapped to this organization (`company_unmapped`)

Content-Type: `application/json`

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

**428** – An existing row was edited or deleted without the version it was read at (`optimistic_lock_required`)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/gusto/people/string/override" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"kind\": \"employee\",
  \"departmentTitle\": null,
  \"capitalizePct\": null,
  \"ramp\": null,
  \"include\": true
}"
```

## GET `/gusto/roster`

Read the synced Gusto people with the client’s headcount rules applied

The one roster read consumed by BOTH the Headcount page (which falls back to its reference data when `live` is false) and the Gusto People page (which also renders the department mapping). It serves them when a Gusto company is CONNECTED and has synced rows for the company this organization is MAPPED to (`live: true`); otherwise it answers 200 with an empty roster and `live: false`. `connected` says a connection row exists and `connectionStatus` says what it is doing (`pending` / `connected` / `error`), so a consent still in flight or a failed connect is not read as "no Gusto at all". `profileDepartments` is the organization’s month-end department profile and `profileStatus` (`ready` / `missing` / `unreadable`) says whether it could be read; a missing or unreadable profile is a state, not an error. `departments` is the four-source mapping union — the synced directory, titles carried by people or person anchors, rule titles, and the always-present default row — with its `rule`, `presentIn`, `personCount` and `duplicateCase`. Every person names the Gusto `departmentTitle`, any `departmentTitleOverride` anchor, the `effectiveDepartmentTitle` the rules resolved, the month-end `department` (canonicalized to the profile’s spelling) and the `departmentSource` (`person` / `department` / `default` / `none`) that supplied it. Requires `gusto.view` and a concrete organization selected. Names are read decrypted through the framework’s encryption layer, scoped to the caller’s organization and tenant; the roster rows are scoped further, to the mapped company.

Requires features: gusto.view

**Tags:** Gusto

**Requires authentication.**

**Features:** gusto.view

### Responses

**200** – The roster: people (employees and contractors), the rules and overrides, the department union, and every problem found

Content-Type: `application/json`

```json
{
  "live": true,
  "connected": true,
  "connectionStatus": null,
  "companyName": null,
  "companyUuid": null,
  "asOf": null,
  "people": [
    {
      "personUuid": "string",
      "kind": "employee",
      "name": "string",
      "title": null,
      "departmentTitle": null,
      "departmentTitleOverride": null,
      "effectiveDepartmentTitle": null,
      "department": null,
      "departmentSource": "person"
    }
  ],
  "problems": [
    {}
  ],
  "rules": [
    {}
  ],
  "overrides": [
    {}
  ],
  "profileDepartments": [
    "string"
  ],
  "profileStatus": "ready",
  "departments": [
    {
      "gustoDepartmentTitle": "string",
      "gustoUuid": null,
      "presentIn": [
        "directory"
      ],
      "rule": null,
      "personCount": 1,
      "isDefault": true,
      "duplicateCase": true
    }
  ]
}
```

**400** – No concrete organization is selected

Content-Type: `application/json`

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

**409** – The request states an organization other than the selected one

Content-Type: `application/json`

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

**500** – The roster could not be read; the page falls back to its reference data

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/gusto/roster" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/gusto/spike`

Record the live MCP catalogue and one read-only call per battery step for the connected company

Runs a fixed read-only battery (17 steps after `tools/list`, one of which may be asked of up to three employees) against the Gusto MCP server through the organization’s own stored grant, for the company that grant reaches, and returns the raw report: what the server said of itself in the handshake, the catalogue exactly as published, the health probe, the company the grant named and whether the organization is mapped to it, the ids carried between calls and where each was read from, and one record per call. A step is asked under the first of its tool’s names the catalogue lists, with arguments named by that tool’s own input schema; its record holds the name used, what the call was about, the arguments sent, timing, error, pagination signals and a truncated raw sample, or says why the step was not attempted. Two steps repeat the employees list with `per: 1` (pages 1 and 2) to show whether the server pages, and one repeats it with `include: all_compensations` to show whether the rate history comes with the roster. A date range a tool requires is given the trailing twelve months, and so is the one the contractor-payments tool declares. Nothing is written and no `data_sync` run is started. The report carries the client’s own data as the server returned it; a credential in a tool’s answer is redacted, and the report never carries a token. Requires `gusto.manage`, a concrete organization selected, and a Gusto connection for that organization. A company mapping is not required: when there is none, the battery takes the company from the grant’s own answers.

Requires features: gusto.manage

**Tags:** Gusto

**Requires authentication.**

**Features:** gusto.manage

### Responses

**200** – The report, as the browser hands it back for download

Content-Type: `application/json`

```json
{
  "generatedAt": "string",
  "sdkVersion": "string",
  "orgScoped": true,
  "server": null,
  "company": null,
  "grantCompany": null,
  "carriedIds": {
    "key": null
  },
  "probe": {},
  "tools": [
    {}
  ],
  "calls": [
    {}
  ],
  "notes": [
    "string"
  ]
}
```

**400** – No concrete organization is selected

Content-Type: `application/json`

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

**409** – Gusto is not connected for this organization (or the connection needs reconnecting), or the stated organization is not the selected one

Content-Type: `application/json`

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

**500** – Unexpected failure while running the battery

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/gusto/spike" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/gusto/sync`

Start one data_sync run for the Gusto company mapped to the selected organization.

Requires features: gusto.manage

**Tags:** Gusto

**Requires authentication.**

**Features:** gusto.manage

### Responses

**200** – Run started

Content-Type: `application/json`

```json
{
  "enqueued": 1,
  "company": "string",
  "runIds": [
    "string"
  ]
}
```

**400** – No concrete organization selected, none stated in `x-obp-organization`, or no company mapped to it

Content-Type: `application/json`

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

**409** – The stated organization is not the one now selected (the page was loaded for another client)

Content-Type: `application/json`

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

**500** – Unexpected failure while starting the run

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/gusto/sync" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/integrations`

List integrations

Returns a paginated collection of integrations.

Requires features: integrations.view

**Tags:** Integrations

**Requires authentication.**

**Features:** integrations.view

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| q | query | any | Optional |
| category | query | any | Optional |
| bundleId | query | any | Optional |
| isEnabled | query | any | Optional |
| healthStatus | query | any | Optional |
| sort | query | any | Optional |
| order | 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 integrations

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "string",
      "title": "string",
      "description": null,
      "category": null,
      "tags": [
        "string"
      ],
      "hub": null,
      "providerKey": null,
      "bundleId": null,
      "author": null,
      "company": null,
      "version": null,
      "hasCredentials": true,
      "isEnabled": true,
      "apiVersion": null,
      "healthStatus": "healthy",
      "lastHealthCheckedAt": null,
      "lastHealthLatencyMs": null,
      "enabledAt": null,
      "analytics": {
        "lastActivityAt": null,
        "totalCount": 1,
        "errorCount": 1,
        "errorRate": 1,
        "dailyCounts": [
          1
        ]
      }
    }
  ],
  "total": 1,
  "totalPages": 1,
  "bundles": [
    {
      "id": "string",
      "title": "string",
      "description": "string",
      "icon": null,
      "integrationCount": 1,
      "enabledCount": 1
    }
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/integrations?order=asc&page=1&pageSize=100" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/integrations/{id}`

Get integration detail

Requires features: integrations.view

**Tags:** Integrations

**Requires authentication.**

**Features:** integrations.view

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/integrations/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/integrations/{id}/credentials`

Get or save integration credentials

Requires features: integrations.credentials.manage

**Tags:** Integrations

**Requires authentication.**

**Features:** integrations.credentials.manage

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/integrations/:id/credentials" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/integrations/{id}/credentials`

Get or save integration credentials

Requires features: integrations.credentials.manage

**Tags:** Integrations

**Requires authentication.**

**Features:** integrations.credentials.manage

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/integrations/:id/credentials" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/integrations/{id}/health`

Run health check for an integration

Requires features: integrations.manage

**Tags:** Integrations

**Requires authentication.**

**Features:** integrations.manage

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

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/integrations/:id/health" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/integrations/{id}/state`

Update integration state

Requires features: integrations.manage

**Tags:** Integrations

**Requires authentication.**

**Features:** integrations.manage

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/integrations/:id/state" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/integrations/{id}/version`

Change integration API version

Requires features: integrations.manage

**Tags:** Integrations

**Requires authentication.**

**Features:** integrations.manage

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/integrations/:id/version" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/integrations/logs`

List integration logs

Requires features: integrations.manage

**Tags:** Integrations

**Requires authentication.**

**Features:** integrations.manage

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/integrations/logs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/ledger/entities`

Latest-period figures per account, one column per entity

Requires features: ledger.view

**Tags:** Ledger

**Requires authentication.**

**Features:** ledger.view

### Responses

**200** – Figures

Content-Type: `application/json`

```json
{
  "seeded": true
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/ledger/entities" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/mail/connection`

Every mailbox this tenant has connected, and each one's current state

Requires features: mail.view

**Tags:** Mail

**Requires authentication.**

**Features:** mail.view

### Responses

**200** – { connections: [{ connectionId, provider, mailboxAddress, mailboxDisplayName, status, lastError, connectedAt, lastPolledAt, organizationId, organizationName, inbound, disconnectable, retainsUnconfirmedGrant }] } — one entry per connection that has a mailbox address and a live client company; rows kept only as connect anchors are omitted, so an empty array means nothing is connected rather than that no row exists

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/mail/connection" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/mail/connection/forget`

Clear the recorded address of a disconnected mailbox (local record only — revokes nothing)

Requires features: mail.connect

**Tags:** Mail

**Requires authentication.**

**Features:** mail.connect

### Responses

**200** – { forgotten: true }

Content-Type: `application/json`

**400** – No connectionId given

Content-Type: `application/json`

**409** – The connection is not disconnected, so there is nothing to forget yet

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/mail/connection/forget" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/mail/inbound`

POST /mail/inbound

**Tags:** Mail

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/mail/inbound" \
  -H "Accept: application/json"
```

## GET `/mail/oauth/callback`

GET /mail/oauth/callback

**Tags:** Mail

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/mail/oauth/callback" \
  -H "Accept: application/json"
```

## POST `/mail/oauth/disconnect`

Disconnect ONE mailbox, named by connectionId (revoke where the vendor allows it, stop polling, then forget it here)

Requires features: mail.connect

**Tags:** Mail

**Requires authentication.**

**Features:** mail.connect

### Responses

**200** – { disconnected: true, revoke } — revoke is revoked | shared | already_ended | nothing_to_revoke | failed | unsupported

Content-Type: `application/json`

**400** – No connectionId given

Content-Type: `application/json`

**500** – Could not disconnect

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/mail/oauth/disconnect" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/mail/oauth/initiate`

POST /mail/oauth/initiate

Requires features: mail.connect

**Tags:** Mail

**Requires authentication.**

**Features:** mail.connect

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/mail/oauth/initiate" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/mail/question-scope`

GET /mail/question-scope

Requires features: mail.view

**Tags:** Mail

**Requires authentication.**

**Features:** mail.view

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/mail/question-scope" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/mail/question-scope`

PUT /mail/question-scope

Requires features: mail.connect

**Tags:** Mail

**Requires authentication.**

**Features:** mail.connect

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/mail/question-scope" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/engine`

Recompute derived subtotals from components (reconciled vs imported) and assert the balance sheet balances every period

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

### Responses

**200** – Engine run

Content-Type: `application/json`

```json
{
  "seeded": true
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/engine" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/export`

Download the seeded model as an Excel workbook (values + live formulas for subtotals)

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

### Responses

**200** – XLSX file

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/export" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/metrics`

ARR, NRR/GRR, Quick Ratio, Net New ARR, Rule of 40, Magic Number, Burn Multiple and Runway, each with provenance

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

### Responses

**200** – SaaS metrics

Content-Type: `application/json`

```json
{
  "seeded": true
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/metrics" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/overview`

Assembled statements, ARR bridge and computed KPIs for the seeded model

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

### Responses

**200** – Model overview

Content-Type: `application/json`

```json
{
  "seeded": true
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/overview" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/workbook/accounts`

The organization's QuickBooks accounts and subtotals a mapping can name, or why there are none yet

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

### Responses

**200** – Options, or the figures’ own status

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/workbook/accounts" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/model/workbook/differences`

Clear the confirmed answer for one difference, keyed on the file pair and the difference key

Requires features: model.manage

**Tags:** Model

**Requires authentication.**

**Features:** model.manage

### Responses

**200** – Count cleared: 0 when this organization had no such note

Content-Type: `application/json`

```json
{
  "deleted": 1
}
```

**400** – No organization selected, or an incomplete or invalid key

Content-Type: `application/json`

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

**409** – A note was cleared against a stale version (optimistic-lock conflict)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/model/workbook/differences" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/model/workbook/differences`

Upsert the confirmed classification and cause for one difference, keyed on the file pair and the difference key

Requires features: model.manage

**Tags:** Model

**Requires authentication.**

**Features:** model.manage

### Responses

**200** – The stored note

Content-Type: `application/json`

```json
{
  "note": {
    "differenceKey": "string",
    "classification": "string",
    "cause": "string",
    "confirmedAt": "string",
    "confirmedByUserId": null,
    "updatedAt": "string"
  }
}
```

**400** – No organization selected, or an incomplete or invalid note

Content-Type: `application/json`

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

**409** – The note was saved against a stale version (optimistic-lock conflict)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/model/workbook/differences" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/model/workbook/inputs/{sourceKey}`

Remove the stored file for one tracked source, and the row that points at it

Tracked sources only, gated exactly as POST is, the `x-obp-organization` header included. 404 when this organization has no upload for that source — including on a repeated DELETE, and including another organization’s upload, which is not found rather than removed. A stored file that cannot be deleted is logged and does not fail the request: the row, which is what the board shows, is gone either way.

Requires features: model.manage

**Tags:** Model

**Requires authentication.**

**Features:** model.manage

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

### Responses

**204** – Removed: the row and its stored file

**400** – No organization selected or none stated in `x-obp-organization`, or a consumed or QuickBooks-report source, which this route does not serve

Content-Type: `application/json`

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

**404** – No source is tracked under that key, or this organization has no upload for it

Content-Type: `application/json`

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

**409** – The `x-obp-organization` header names another organization than the one selected: the page was loaded for another client

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/model/workbook/inputs/:sourceKey" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/model/workbook/inputs/{sourceKey}`

Upload one tracked source, read on arrival (multipart/form-data: `file`, and `month` as YYYY-MM)

Tracked sources only: `invoices_and_payments`, `headcount`, `hubspot_pipeline`, `hubspot_closed_won`, `ltv_cac`, `budget`, `ar_aging_detail`, `ttm_by_department`, `budget_to_actual`, `close_backup`, `close_sop`, `staff_model_workbook` and `staff_quickbooks_export`. A consumed source (the model workbook, the QuickBooks rows) and the QuickBooks report kinds are refused — the latter go through POST /api/qbo_reports/imports. The `x-obp-organization` header must name the organization the page shows, which must be the one selected. `month` is the month the file is for. A file of the month’s reporting package must send one; the close SOP, the annual budget and the team’s workbook take one when given and need none. A package file must be what its row holds (`.docx` for the close SOP, `.xlsx` for every other), and every QuickBooks report inside it must name this organization’s own QuickBooks company. A file that is refused is answered 400 with its problem list, and is not stored. A re-upload replaces the source’s row and removes the file it superseded; a superseded file that cannot be deleted is logged server-side and still answers 201, the upload itself having succeeded.

Requires features: model.manage

**Tags:** Model

**Requires authentication.**

**Features:** model.manage

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

### Responses

**201** – The stored source: its summary, the date it states, the month it is for and what it was recognised as

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected or none stated in `x-obp-organization`, a consumed or QuickBooks-report source, no file, an extension the row does not take, no month for a package file or a month that is not YYYY-MM, a file whose expansion is unbounded, or a refused file (another row’s file, another company’s report, or its own parser’s problems)

Content-Type: `application/json`

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

**404** – No source is tracked under that key

Content-Type: `application/json`

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

**409** – The `x-obp-organization` header names another organization than the one selected: the page was loaded for another client

Content-Type: `application/json`

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

**413** – Upload exceeds 25 MB

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/model/workbook/inputs/:sourceKey" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/model/workbook/mappings`

Forget one or more saved mappings, each keyed on (statement, fingerprint) — the row is matched automatically again

Requires features: model.manage

**Tags:** Model

**Requires authentication.**

**Features:** model.manage

### Responses

**200** – Count deleted

Content-Type: `application/json`

```json
{
  "deleted": 1
}
```

**400** – No organization selected, or invalid input

Content-Type: `application/json`

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

**409** – A named row was deleted against a stale version (optimistic-lock conflict)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/model/workbook/mappings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/model/workbook/mappings`

Upsert one or more confirmed mappings, each keyed on (statement, fingerprint)

Requires features: model.manage

**Tags:** Model

**Requires authentication.**

**Features:** model.manage

### Responses

**200** – Count saved

Content-Type: `application/json`

```json
{
  "saved": 1
}
```

**400** – No organization selected, or invalid mappings

Content-Type: `application/json`

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

**409** – A named row was saved against a stale version (optimistic-lock conflict)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/model/workbook/mappings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/workbook/runs`

List the organization's workbook update runs, newest first

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

### Responses

**200** – Runs

Content-Type: `application/json`

```json
{
  "items": [
    {}
  ]
}
```

**400** – No organization selected, or an invalid limit

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/workbook/runs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/model/workbook/runs`

Start a workbook update run from an uploaded .xlsx or a stored run (multipart/form-data)

Requires features: model.manage

**Tags:** Model

**Requires authentication.**

**Features:** model.manage

### Responses

**201** – The run (may be status: failed)

Content-Type: `application/json`

```json
{}
```

**400** – No organization, no stated organization for an uploaded file, no file/fromRunId, or a non-.xlsx upload

Content-Type: `application/json`

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

**404** – fromRunId names no run of this organization with a stored file

Content-Type: `application/json`

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

**413** – Upload exceeds 25 MB

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/model/workbook/runs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/workbook/runs/{id}`

One run, in full: layout, figures, tie-outs, mapping review, changes

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

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

### Responses

**200** – Run detail

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected

Content-Type: `application/json`

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

**404** – No such run in this organization, or a malformed id

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/workbook/runs/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/workbook/runs/{id}/file`

Stream a run's output (default), source, or compared-staff-workbook .xlsx bytes

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

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

### Responses

**200** – XLSX file

Content-Type: `application/json`

**400** – No organization selected, or a kind that is not output, source or staff

Content-Type: `application/json`

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

**404** – No such run, it kept no file of that kind (including a run that compared nothing), or a malformed id

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/workbook/runs/:id/file" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/workbook/sessions`

The organization's open workbook update draft and its input progress, or null

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

### Responses

**200** – The open draft and its progress, both null when none is open, and the `organizationId` that answered

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/workbook/sessions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/model/workbook/sessions`

Open (or replace) the draft from an uploaded .xlsx (multipart/form-data). Runs nothing. The request states the organization its page shows in `x-obp-organization`.

Requires features: model.manage

**Tags:** Model

**Requires authentication.**

**Features:** model.manage

### Responses

**201** – The draft

Content-Type: `application/json`

```json
{}
```

**400** – No organization, a request that does not state the organization its page shows, no file, a non-.xlsx upload, no target month, an unreadable file, or a layout refusal

Content-Type: `application/json`

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

**409** – Another create won the race (its draft is in the body), or the page shows another organization than the one now selected (`organization_changed`)

Content-Type: `application/json`

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

**413** – Upload exceeds 25 MB

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/model/workbook/sessions" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/model/workbook/sessions/{id}`

Discard an OPEN draft and its stored file

Requires features: model.manage

**Tags:** Model

**Requires authentication.**

**Features:** model.manage

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

### Responses

**200** – Discarded

Content-Type: `application/json`

```json
{
  "id": "string"
}
```

**400** – No organization selected

Content-Type: `application/json`

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

**404** – No such draft in this organization, or a malformed id

Content-Type: `application/json`

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

**409** – The draft has run; its file belongs to that run

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/model/workbook/sessions/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/workbook/sessions/{id}`

One draft of this organization, whatever its status

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

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

### Responses

**200** – The draft

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected

Content-Type: `application/json`

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

**404** – No such draft in this organization, or a malformed id

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/workbook/sessions/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/workbook/sessions/{id}/inputs`

The draft’s inputs board: every source it needs, its state, and why a run cannot start

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

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

### Responses

**200** – The board (a `SourceBoard`, at the top level) with the `organizationId` it answers for

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected

Content-Type: `application/json`

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

**404** – No such draft in this organization, or a malformed id

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/workbook/sessions/:id/inputs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/model/workbook/sessions/{id}/run`

Run the update over the draft’s stored workbook and return the run

Requires features: model.manage

**Tags:** Model

**Requires authentication.**

**Features:** model.manage

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

### Responses

**201** – The run (may be status: failed)

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected

Content-Type: `application/json`

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

**404** – No such draft in this organization, or a malformed id

Content-Type: `application/json`

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

**409** – The draft has already run

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/model/workbook/sessions/:id/run" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/model/workbook/settings`

Upsert the expected balance-sheet variance, the fiscal-year-start override and the model horizon

Requires features: model.manage

**Tags:** Model

**Requires authentication.**

**Features:** model.manage

### Responses

**200** – Saved settings, including which sources this client needs

Content-Type: `application/json`

```json
{
  "expectedBalanceSheetVariance": 1,
  "fiscalYearStartMonth": null,
  "horizonEndMonth": null,
  "sources": null,
  "updatedAt": null
}
```

**400** – No organization selected, or invalid settings

Content-Type: `application/json`

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

**409** – Saved against a stale version (optimistic-lock conflict)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/model/workbook/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/model/workbook/status`

The figures' readiness, the month a default run would close, settings, saved mapping count and the last run

Requires features: model.view

**Tags:** Model

**Requires authentication.**

**Features:** model.view

### Responses

**200** – Status

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/model/workbook/status" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/month_end/packages/{id}/file`

Stream a built package’s zip, under the name the build gave it

Requires features: month_end.view

**Tags:** Month-End

**Requires authentication.**

**Features:** month_end.view

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

### Responses

**200** – The zip

Content-Type: `application/zip`

**400** – No organization selected

Content-Type: `application/json`

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

**404** – No such package in this organization, or a malformed id (`package_not_found`)

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "problems": [
    {
      "key": "string",
      "fallback": "string"
    }
  ]
}
```

**409** – The selected organization is not the one the request says its page shows (`organization_changed`)

Content-Type: `application/json`

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

**500** – The stored zip is not the one the package was built as (`zip_changed`), or an unexpected failure

Content-Type: `application/json`

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

**503** – The zip could not be read from file storage (`storage_unavailable`)

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "problems": [
    {
      "key": "string",
      "fallback": "string"
    }
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/month_end/packages/:id/file" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/month_end/packages/{id}/sent`

Mark this package as the one sent for its month; the newest mark wins

The body is `{}` or `{ "confirm": true }`; send the organization the page shows in `x-obp-organization`. A package that is not ready is marked only with the confirmation; without it the answer is 409 and nothing is written.

Requires features: month_end.manage

**Tags:** Month-End

**Requires authentication.**

**Features:** month_end.manage

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

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – The package as marked (a `PackageSummary`, with `sentAt` and `sentByUserId`)

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected, none stated by the page (`organization_not_stated`), or a body that says anything but `{ "confirm": true }` (`invalid_request`)

Content-Type: `application/json`

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

**404** – No such package in this organization, or a malformed id (`package_not_found`)

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "problems": [
    {
      "key": "string",
      "fallback": "string"
    }
  ]
}
```

**409** – The package is not ready and the request did not confirm (`not_ready`), or the selected organization is no longer the one the page shows (`organization_changed`)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/month_end/packages/:id/sent" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## GET `/month_end/profile`

The client’s email settings, with the version an edit is made against

Requires features: month_end.view

**Tags:** Month-End

**Requires authentication.**

**Features:** month_end.view

### Responses

**200** – The settings and their `updatedAt`

Content-Type: `application/json`

```json
{
  "profile": {
    "clientName": "string",
    "recipientName": "string",
    "arContactName": "string",
    "filePrefix": "string",
    "stageRollup": [
      {
        "label": "string",
        "stages": [
          "string"
        ]
      }
    ],
    "excludedStages": [
      "string"
    ],
    "normalisationAccounts": [
      "string"
    ],
    "newLogoDealType": "string",
    "topCustomers": 1,
    "collectionDaysGoal": 1,
    "driverThreshold": 1,
    "didNotRepeatThreshold": 1,
    "departments": [
      {
        "name": "string",
        "short": "string"
      }
    ],
    "senderName": "string"
  },
  "updatedAt": "string"
}
```

**400** – No organization selected

Content-Type: `application/json`

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

**404** – This client has no settings yet (`no_settings`)

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "problems": [
    {
      "key": "string",
      "fallback": "string"
    }
  ]
}
```

**409** – The selected organization is not the one the page says it shows (`organization_changed`)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

**503** – The settings are stored but could not be read back (`settings_unreadable`)

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "problems": [
    {
      "key": "string",
      "fallback": "string"
    }
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/month_end/profile" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/month_end/profile`

Validate and save the client’s email settings

The body is `{ "profile": { … } }`. Send the `updatedAt` the settings were read with in the `x-om-ext-optimistic-lock-expected-updated-at` header; a save made against settings that have since changed is answered 409 and changes nothing, and one that names no version is taken only while the client has no settings or has settings that cannot be read back. Send the organization the page shows in `x-obp-organization`. Settings the schema refuses are answered 400 with one problem per line, each at its field. The settings are stored encrypted or not at all.

Requires features: month_end.manage

**Tags:** Month-End

**Requires authentication.**

**Features:** month_end.manage

### Request Body

Content-Type: `application/json`

```json
{
  "profile": {
    "clientName": "string",
    "recipientName": "string",
    "arContactName": "string",
    "filePrefix": "string",
    "stageRollup": [
      {
        "label": "string",
        "stages": [
          "string"
        ]
      }
    ],
    "excludedStages": [
      "string"
    ],
    "normalisationAccounts": [
      "string"
    ],
    "newLogoDealType": "string",
    "topCustomers": 1,
    "collectionDaysGoal": 1,
    "driverThreshold": 1,
    "didNotRepeatThreshold": 1,
    "departments": [
      {
        "name": "string",
        "short": "string"
      }
    ],
    "senderName": "string"
  }
}
```

### Responses

**200** – The saved settings and their new `updatedAt`

Content-Type: `application/json`

```json
{
  "profile": {
    "clientName": "string",
    "recipientName": "string",
    "arContactName": "string",
    "filePrefix": "string",
    "stageRollup": [
      {
        "label": "string",
        "stages": [
          "string"
        ]
      }
    ],
    "excludedStages": [
      "string"
    ],
    "normalisationAccounts": [
      "string"
    ],
    "newLogoDealType": "string",
    "topCustomers": 1,
    "collectionDaysGoal": 1,
    "driverThreshold": 1,
    "didNotRepeatThreshold": 1,
    "departments": [
      {
        "name": "string",
        "short": "string"
      }
    ],
    "senderName": "string"
  },
  "updatedAt": "string"
}
```

**400** – No organization selected, none stated by the page (`organization_not_stated`), a body that is not `{ "profile": … }` (`invalid_request`), or settings the schema refuses (`invalid_settings`)

Content-Type: `application/json`

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

**409** – Saved against a stale version, or lost to another first save (`optimistic_lock_conflict`); saved with no version against settings that read back (`version_required`); or the selected organization is no longer the one the page shows (`organization_changed`)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

**503** – Nothing was written: encryption is not set up for the settings (`encryption_not_set_up`), or they would not have been stored encrypted (`not_stored_encrypted`)

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "problems": [
    {
      "key": "string",
      "fallback": "string"
    }
  ]
}
```

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/month_end/profile" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"profile\": {
    \"clientName\": \"string\",
    \"recipientName\": \"string\",
    \"arContactName\": \"string\",
    \"filePrefix\": \"string\",
    \"stageRollup\": [
      {
        \"label\": \"string\",
        \"stages\": [
          \"string\"
        ]
      }
    ],
    \"excludedStages\": [
      \"string\"
    ],
    \"normalisationAccounts\": [
      \"string\"
    ],
    \"newLogoDealType\": \"string\",
    \"topCustomers\": 1,
    \"collectionDaysGoal\": 1,
    \"driverThreshold\": 1,
    \"didNotRepeatThreshold\": 1,
    \"departments\": [
      {
        \"name\": \"string\",
        \"short\": \"string\"
      }
    ],
    \"senderName\": \"string\"
  }
}"
```

## GET `/month_end/runs/{runId}/package`

The package page’s data: the plan, the newest package built from this run, the month’s sent package, and where last month’s figures come from

Requires features: month_end.view

**Tags:** Month-End

**Requires authentication.**

**Features:** month_end.view

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

### Responses

**200** – The page’s data (a `PackagePageData`, at the top level)

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected

Content-Type: `application/json`

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

**404** – No such model update in this organization, or a malformed id (`run_not_found`)

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "problems": [
    {
      "key": "string",
      "fallback": "string"
    }
  ]
}
```

**409** – The selected organization is not the one the page says it shows (`organization_changed`)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

**503** – The client’s settings (`settings_unreadable`) or the package to show (`package_unreadable`) are stored but could not be read back

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "problems": [
    {
      "key": "string",
      "fallback": "string"
    }
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/month_end/runs/:runId/package" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/month_end/runs/{runId}/package`

Build the reporting package: the zip of the month’s files and the email written beside it

No body; send the organization the page shows in `x-obp-organization`. Answers 201 with the new package’s summary, whether or not the package is ready to send. A build the plan blocks is answered 409 with the plan’s own `blocks`, the same list the page shows. A stored file that cannot be read back, is no longer the file that was uploaded, or does not read as its row is answered 400 naming the row (`component`, `sourceKey`); files holding another company’s QuickBooks report are answered 400 with one line per file and sheet. Whatever the refusal, nothing is kept.

Requires features: month_end.manage

**Tags:** Month-End

**Requires authentication.**

**Features:** month_end.manage

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

### Responses

**201** – The package that was built (a `PackageSummary`)

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected, or none stated by the page (`organization_not_stated`); a stored file that could not be read (`file_unreadable`); one whose bytes no longer match its upload (`file_changed`); or files holding another company’s QuickBooks report (`file_other_company`)

Content-Type: `application/json`

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

**404** – No such model update in this organization, or a malformed id (`run_not_found`)

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "problems": [
    {
      "key": "string",
      "fallback": "string"
    }
  ]
}
```

**409** – The build is blocked (`blocked`): `blocks` lists why, each with what to do. Or the selected organization is no longer the one the page shows (`organization_changed`)

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

**503** – Nothing was kept: the settings, or last month’s sent package, are stored but could not be read back (`settings_unreadable`, `package_unreadable`); encryption is not set up for a package’s content (`encryption_not_set_up`, `not_stored_encrypted`); or file storage refused the zip (`storage_unavailable`)

Content-Type: `application/json`

```json
{
  "error": "string",
  "code": "string",
  "problems": [
    {
      "key": "string",
      "fallback": "string"
    }
  ]
}
```

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/month_end/runs/:runId/package" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/notifications?page=1&pageSize=20" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/notifications`

Create notification

Creates a notification for a user.

Requires features: notifications.create

**Tags:** Notifications

**Requires authentication.**

**Features:** notifications.create

### 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://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/api/notifications/:id/restore" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/notifications/batch`

POST /notifications/batch

Requires features: notifications.create

**Tags:** Notifications

**Requires authentication.**

**Features:** notifications.create

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/notifications/batch" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/notifications/feature`

POST /notifications/feature

Requires features: notifications.create

**Tags:** Notifications

**Requires authentication.**

**Features:** notifications.create

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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://open-book-partners-erp.northbound.run/api/notifications/mark-all-read" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/notifications/role`

POST /notifications/role

Requires features: notifications.create

**Tags:** Notifications

**Requires authentication.**

**Features:** notifications.create

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/notifications/role" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/notifications/settings`

GET /notifications/settings

Requires features: notifications.manage

**Tags:** Notifications

**Requires authentication.**

**Features:** notifications.manage

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/notifications/settings" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/notifications/settings`

POST /notifications/settings

Requires features: notifications.manage

**Tags:** Notifications

**Requires authentication.**

**Features:** notifications.manage

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/notifications/settings" \
  -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://open-book-partners-erp.northbound.run/api/notifications/unread-count" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/obligations/checklist`

Completeness of one client: present, missing (named) and unverified required slots

Requires features: obligations.view

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.view

### Responses

**200** – Checklist state and its per-requirement items

Content-Type: `application/json`

```json
{
  "state": "complete",
  "items": [
    {}
  ],
  "present": [
    {}
  ],
  "missing": [
    {}
  ],
  "unverified": [
    {}
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/obligations/checklist" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/obligations/proposals`

Proposals (including those needing attention), scoped to the selected company

Requires features: obligations.view

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.view

### Responses

**200** – Proposals

Content-Type: `application/json`

```json
{
  "items": [
    {}
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/obligations/proposals" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/obligations/proposals`

Create a proposal from an uploaded document (never a register row)

Requires features: obligations.manage

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.manage

### Request Body

Content-Type: `application/json`

```json
{}
```

### Responses

**200** – The proposal id and its status

Content-Type: `application/json`

```json
{
  "proposalId": "string",
  "status": "string"
}
```

**400** – No file, a bad kind, or no organization selected

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/obligations/proposals" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{}"
```

## GET `/obligations/proposals/{id}`

Read one proposal, scoped to the selected company

Requires features: obligations.view

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.view

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

### Responses

**200** – The proposal

Content-Type: `application/json`

```json
{
  "item": {}
}
```

**404** – No such proposal

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/obligations/proposals/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/obligations/proposals/{id}`

Persist corrections to a proposal, or reclassify it to another register kind

Requires features: obligations.manage

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.manage

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

### Request Body

Content-Type: `application/json`

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

### Responses

**200** – The updated proposal

Content-Type: `application/json`

```json
{
  "item": {}
}
```

**409** – The proposal was already confirmed or discarded

Content-Type: `application/json`

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

### Example

```bash
curl -X PATCH "https://open-book-partners-erp.northbound.run/api/obligations/proposals/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"fields\": {
    \"key\": {
      \"value\": \"string\"
    }
  }
}"
```

## POST `/obligations/proposals/{id}/confirm`

Create the register row from the proposal, once (idempotent)

Requires features: obligations.manage

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.manage

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

### Responses

**200** – The confirmed row id (and whether it was newly created)

Content-Type: `application/json`

```json
{
  "confirmed": true,
  "created": true,
  "recordId": null
}
```

**404** – No such proposal

Content-Type: `application/json`

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

**409** – The proposal needs attention first

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/obligations/proposals/:id/confirm" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/obligations/proposals/{id}/discard`

Mark the proposal discarded; the stored document remains for audit

Requires features: obligations.manage

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.manage

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

### Responses

**200** – The discarded proposal id and status

Content-Type: `application/json`

```json
{
  "id": "string",
  "status": "string"
}
```

**404** – No such proposal

Content-Type: `application/json`

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

**409** – The proposal was already confirmed

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/obligations/proposals/:id/discard" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/obligations/records/{id}`

The register row (or its still-unconfirmed proposal), document of record, and linked records

Requires features: obligations.view

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.view

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

### Responses

**200** – The detail payload

Content-Type: `application/json`

```json
{
  "id": "string",
  "kind": null
}
```

**404** – No such record or proposal

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/obligations/records/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/obligations/records/{id}`

Edit a confirmed row — requires the optimistic-lock version header

Requires features: obligations.manage

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.manage

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

### Request Body

Content-Type: `application/json`

```json
{
  "fields": {}
}
```

### Responses

**200** – The updated row

Content-Type: `application/json`

```json
{
  "record": {}
}
```

**409** – A concurrent edit already changed the row

Content-Type: `application/json`

```json
{
  "code": "string"
}
```

**428** – No version header was sent

Content-Type: `application/json`

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

### Example

```bash
curl -X PATCH "https://open-book-partners-erp.northbound.run/api/obligations/records/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"fields\": {}
}"
```

## GET `/obligations/registers/contracts`

Vendor contracts for the selected company, with term end and notice window

Requires features: obligations.view

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.view

### Responses

**200** – Contracts

Content-Type: `application/json`

```json
{
  "items": [
    {}
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/obligations/registers/contracts" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/obligations/registers/insurance`

Policies and COIs for the selected company, as separate arrays (never merged)

Requires features: obligations.view

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.view

### Responses

**200** – Policies and certificates

Content-Type: `application/json`

```json
{
  "policies": [
    {}
  ],
  "certificates": [
    {}
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/obligations/registers/insurance" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/obligations/registers/leases`

Leases for the selected company, including their term type

Requires features: obligations.view

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.view

### Responses

**200** – Leases

Content-Type: `application/json`

```json
{
  "items": [
    {}
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/obligations/registers/leases" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/obligations/renewals`

Raw renewal candidates (policy/COI expiry, lease end, contract notice deadline) for all visible clients

Requires features: obligations.view

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.view

### Responses

**200** – Renewal candidates

Content-Type: `application/json`

```json
{
  "items": [
    {}
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/obligations/renewals" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/obligations/requirements`

List the requirements a client checklist is evaluated against

Requires features: obligations.view

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.view

### Responses

**200** – Requirements

Content-Type: `application/json`

```json
{
  "items": [
    {}
  ]
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/obligations/requirements" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/obligations/requirements`

Define one required slot (insurance | lease | contract) for a client

Requires features: obligations.manage

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.manage

### Request Body

Content-Type: `application/json`

```json
{
  "kind": "insurance",
  "label": null
}
```

### Responses

**200** – The created requirement

Content-Type: `application/json`

```json
{
  "requirement": {}
}
```

**400** – An unknown kind, a bad count, or no organization selected

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/obligations/requirements" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"kind\": \"insurance\",
  \"label\": null
}"
```

## DELETE `/obligations/requirements/{id}`

Soft-delete a requirement — requires the optimistic-lock version header

Requires features: obligations.manage

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.manage

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

### Responses

**200** – The requirement was removed

Content-Type: `application/json`

```json
{
  "deleted": true,
  "id": "string"
}
```

**404** – No such requirement

Content-Type: `application/json`

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

**409** – A concurrent edit already changed the requirement

Content-Type: `application/json`

```json
{
  "code": "string"
}
```

**428** – No version header was sent

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/obligations/requirements/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PATCH `/obligations/requirements/{id}`

Edit a requirement — requires the optimistic-lock version header

Requires features: obligations.manage

**Tags:** Obligations

**Requires authentication.**

**Features:** obligations.manage

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

### Request Body

Content-Type: `application/json`

```json
{
  "label": null
}
```

### Responses

**200** – The updated requirement

Content-Type: `application/json`

```json
{
  "requirement": {}
}
```

**400** – An unknown kind or a bad count

Content-Type: `application/json`

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

**404** – No such requirement

Content-Type: `application/json`

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

**409** – A concurrent edit already changed the requirement

Content-Type: `application/json`

```json
{
  "code": "string"
}
```

**428** – No version header was sent

Content-Type: `application/json`

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

### Example

```bash
curl -X PATCH "https://open-book-partners-erp.northbound.run/api/obligations/requirements/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"label\": null
}"
```

## GET `/perspectives/{tableId}`

Load perspectives for a table

Returns personal perspectives and available role defaults for the requested table identifier.

Requires features: perspectives.use

**Tags:** Perspectives

**Requires authentication.**

**Features:** perspectives.use

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/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.

Requires features: perspectives.use

**Tags:** Perspectives

**Requires authentication.**

**Features:** perspectives.use

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

**409** – Optimistic lock conflict or perspective name already exists

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: perspectives.use

**Tags:** Perspectives

**Requires authentication.**

**Features:** perspectives.use

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

**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://open-book-partners-erp.northbound.run/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.

Requires features: perspectives.role_defaults

**Tags:** Perspectives

**Requires authentication.**

**Features:** perspectives.role_defaults

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

**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://open-book-partners-erp.northbound.run/api/perspectives/string/roles/00000000-0000-4000-8000-000000000000" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/progress/active`

GET /progress/active

Requires features: progress.view

**Tags:** Progress

**Requires authentication.**

**Features:** progress.view

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/progress/active" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/progress/jobs`

List progressjobs

Returns a paginated collection of progressjobs scoped to the authenticated tenant.

Requires features: progress.view

**Tags:** Progress

**Requires authentication.**

**Features:** progress.view

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| status | query | any | Optional |
| jobType | query | any | Optional |
| parentJobId | query | any | Optional |
| includeCompleted | query | any | Optional |
| completedSince | query | any | Optional |
| 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 progressjobs

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "jobType": "string",
      "name": "string",
      "description": null,
      "status": "string",
      "progressPercent": 1,
      "processedCount": 1,
      "totalCount": null,
      "etaSeconds": null,
      "cancellable": true,
      "startedAt": null,
      "finishedAt": null,
      "errorMessage": null,
      "createdAt": null,
      "tenantId": "00000000-0000-4000-8000-000000000000",
      "organizationId": null
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/progress/jobs?page=1&pageSize=20" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/progress/jobs`

Create progressjob

Creates a new progress job for tracking a long-running operation.

Requires features: progress.create

**Tags:** Progress

**Requires authentication.**

**Features:** progress.create

### Request Body

Content-Type: `application/json`

```json
{
  "jobType": "string",
  "name": "string",
  "cancellable": false
}
```

### Responses

**201** – ProgressJob created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/progress/jobs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"jobType\": \"string\",
  \"name\": \"string\",
  \"cancellable\": false
}"
```

## DELETE `/progress/jobs/{id}`

DELETE /progress/jobs/{id}

Requires features: progress.cancel

**Tags:** Progress

**Requires authentication.**

**Features:** progress.cancel

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

### Responses

**204** – Success

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/progress/jobs/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/progress/jobs/{id}`

GET /progress/jobs/{id}

Requires features: progress.view

**Tags:** Progress

**Requires authentication.**

**Features:** progress.view

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/progress/jobs/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## PUT `/progress/jobs/{id}`

PUT /progress/jobs/{id}

Requires features: progress.update

**Tags:** Progress

**Requires authentication.**

**Features:** progress.update

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

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/progress/jobs/:id" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/qbo_reports/imports`

Upload a QuickBooks report export (.xlsx or .xlsm, multipart/form-data) and index it against the organization's confirmed company (qbo_reports.manage)

Requires features: qbo_reports.manage

**Tags:** QuickBooks reports

**Requires authentication.**

**Features:** qbo_reports.manage

### Responses

**201** – The record outcome and the landing attempt

Content-Type: `application/json`

```json
{}
```

**400** – No organization selected, no file, not a spreadsheet, or a report that could not be indexed

Content-Type: `application/json`

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

**409** – The organization's books come from another source, the report names another company, or the page that sent it shows another organization than the one now selected (`organization_changed`)

Content-Type: `application/json`

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

**413** – Upload exceeds 25 MB

Content-Type: `application/json`

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

**500** – Unexpected failure

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/qbo_reports/imports" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/query_index/purge`

Purge query index records

Queues a purge job to remove indexed records for an entity type within the active scope.

Requires features: query_index.purge

**Tags:** Query Index

**Requires authentication.**

**Features:** query_index.purge

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: query_index.reindex

**Tags:** Query Index

**Requires authentication.**

**Features:** query_index.reindex

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/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.

Requires features: query_index.status.view

**Tags:** Query Index

**Requires authentication.**

**Features:** query_index.status.view

### Responses

**200** – Current query index status.

Content-Type: `application/json`

```json
{
  "items": [
    {
      "entityId": "string",
      "label": "string",
      "baseCount": null,
      "indexCount": null,
      "vectorCount": null,
      "ok": true,
      "job": {
        "status": "idle",
        "startedAt": null,
        "finishedAt": null,
        "heartbeatAt": null,
        "processedCount": null,
        "totalCount": null,
        "scope": null
      }
    }
  ],
  "errors": [
    {
      "id": "string",
      "source": "string",
      "handler": "string",
      "entityType": null,
      "recordId": null,
      "tenantId": null,
      "organizationId": null,
      "message": "string",
      "stack": null,
      "payload": null,
      "occurredAt": "string"
    }
  ],
  "logs": [
    {
      "id": "string",
      "source": "string",
      "handler": "string",
      "level": "info",
      "entityType": null,
      "recordId": null,
      "tenantId": null,
      "organizationId": null,
      "message": "string",
      "details": null,
      "occurredAt": "string"
    }
  ]
}
```

**400** – Tenant or organization context required

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/query_index/status" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## DELETE `/scheduler/jobs`

Delete scheduledjob

Deletes a scheduled job by ID.

Requires features: scheduler.jobs.manage

**Tags:** Scheduler

**Requires authentication.**

**Features:** scheduler.jobs.manage

### Request Body

Content-Type: `application/json`

```json
{
  "id": "string"
}
```

### Responses

**200** – ScheduledJob deleted

Content-Type: `application/json`

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

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/scheduler/jobs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"string\"
}"
```

## GET `/scheduler/jobs`

List scheduledjobs

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

Requires features: scheduler.jobs.view

**Tags:** Scheduler

**Requires authentication.**

**Features:** scheduler.jobs.view

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| page | query | any | Optional |
| pageSize | query | any | Optional |
| id | query | any | Optional |
| search | query | any | Optional |
| scopeType | query | any | Optional |
| isEnabled | query | any | Required |
| sourceType | query | any | Optional |
| sourceModule | query | any | Optional |
| sort | query | any | Optional |
| order | query | any | Optional |
| ids | query | any | Optional. Comma-separated list of record UUIDs to filter by (max 200). |

### Responses

**200** – Paginated scheduledjobs

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "string",
      "description": null,
      "scopeType": "system",
      "organizationId": null,
      "tenantId": null,
      "scheduleType": "cron",
      "scheduleValue": "string",
      "timezone": "string",
      "targetType": "queue",
      "targetQueue": null,
      "targetCommand": null,
      "targetPayload": null,
      "requireFeature": null,
      "isEnabled": true,
      "lastRunAt": null,
      "nextRunAt": null,
      "sourceType": "user",
      "sourceModule": null,
      "createdAt": "string",
      "updatedAt": "string"
    }
  ],
  "total": 1,
  "totalPages": 1
}
```

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/scheduler/jobs?page=1&pageSize=20" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/scheduler/jobs`

Create scheduledjob

Creates a new scheduled job with cron or interval-based scheduling.

Requires features: scheduler.jobs.manage

**Tags:** Scheduler

**Requires authentication.**

**Features:** scheduler.jobs.manage

### Request Body

Content-Type: `application/json`

```json
{
  "name": "string",
  "description": null,
  "scopeType": "system",
  "organizationId": null,
  "tenantId": null,
  "scheduleType": "cron",
  "scheduleValue": "string",
  "timezone": "UTC",
  "targetType": "queue",
  "targetQueue": null,
  "targetCommand": null,
  "targetPayload": null,
  "requireFeature": null,
  "isEnabled": true,
  "sourceType": "user",
  "sourceModule": null
}
```

### Responses

**201** – ScheduledJob created

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/scheduler/jobs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"name\": \"string\",
  \"description\": null,
  \"scopeType\": \"system\",
  \"organizationId\": null,
  \"tenantId\": null,
  \"scheduleType\": \"cron\",
  \"scheduleValue\": \"string\",
  \"timezone\": \"UTC\",
  \"targetType\": \"queue\",
  \"targetQueue\": null,
  \"targetCommand\": null,
  \"targetPayload\": null,
  \"requireFeature\": null,
  \"isEnabled\": true,
  \"sourceType\": \"user\",
  \"sourceModule\": null
}"
```

## PUT `/scheduler/jobs`

Update scheduledjob

Updates an existing scheduled job by ID.

Requires features: scheduler.jobs.manage

**Tags:** Scheduler

**Requires authentication.**

**Features:** scheduler.jobs.manage

### Request Body

Content-Type: `application/json`

```json
{
  "id": "string",
  "description": null,
  "targetQueue": null,
  "targetCommand": null,
  "targetPayload": null,
  "requireFeature": null
}
```

### Responses

**200** – ScheduledJob updated

Content-Type: `application/json`

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

### Example

```bash
curl -X PUT "https://open-book-partners-erp.northbound.run/api/scheduler/jobs" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"string\",
  \"description\": null,
  \"targetQueue\": null,
  \"targetCommand\": null,
  \"targetPayload\": null,
  \"requireFeature\": null
}"
```

## GET `/scheduler/jobs/{id}/executions`

Get execution history for a schedule

Fetch recent executions from BullMQ for a scheduled job. Requires QUEUE_STRATEGY=async.

**Tags:** Scheduler

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

### Responses

**200** – Execution history

Content-Type: `application/json`

```json
{
  "items": [
    {
      "id": "string",
      "scheduleId": "00000000-0000-4000-8000-000000000000",
      "startedAt": "string",
      "finishedAt": null,
      "status": "running",
      "triggerType": "scheduled",
      "triggeredByUserId": null,
      "errorMessage": null,
      "errorStack": null,
      "durationMs": null,
      "queueJobId": "string",
      "queueName": "string",
      "attemptsMade": 1,
      "result": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 1
}
```

**400** – Local strategy not supported

Content-Type: `application/json`

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

**401** – Unauthorized

Content-Type: `application/json`

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

**403** – Access denied

Content-Type: `application/json`

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

**404** – Schedule not found

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/scheduler/jobs/:id/executions?pageSize=20" \
  -H "Accept: application/json"
```

## GET `/scheduler/queue-jobs/{jobId}`

Get BullMQ job details and logs

Fetch detailed information and logs for a queue job. Requires QUEUE_STRATEGY=async.

**Tags:** Scheduler

### Parameters
| Name | Location | Type | Description |
| --- | --- | --- | --- |
| jobId | path | any | Required |
| queue | query | any | Required |

### Responses

**200** – Job details and logs

Content-Type: `application/json`

```json
{
  "id": "string",
  "name": "string",
  "state": "waiting",
  "progress": null,
  "returnvalue": null,
  "failedReason": null,
  "stacktrace": null,
  "attemptsMade": 1,
  "processedOn": null,
  "finishedOn": null,
  "logs": [
    "string"
  ]
}
```

**400** – Invalid request or local strategy not supported

Content-Type: `application/json`

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

**401** – Unauthorized

Content-Type: `application/json`

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

**403** – Access denied

Content-Type: `application/json`

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

**404** – Job not found

Content-Type: `application/json`

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

**500** – Internal server error

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/scheduler/queue-jobs/:jobId?queue=string" \
  -H "Accept: application/json"
```

## GET `/scheduler/targets`

List available queues and commands

Returns all registered queue names (from module workers) and command IDs (from the command registry) that can be used as schedule targets.

**Tags:** Scheduler

### Responses

**200** – Available targets

Content-Type: `application/json`

```json
{
  "queues": [
    {
      "value": "string",
      "label": "string"
    }
  ],
  "commands": [
    {
      "value": "string",
      "label": "string"
    }
  ]
}
```

**401** – Unauthorized

Content-Type: `application/json`

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

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/scheduler/targets" \
  -H "Accept: application/json"
```

## POST `/scheduler/trigger`

Manually trigger a schedule

Executes a scheduled job immediately, bypassing the scheduled time. Only works with async queue strategy.

**Tags:** Scheduler

### Request Body

Content-Type: `application/json`

```json
{
  "id": "string"
}
```

### Responses

**200** – Schedule triggered successfully

Content-Type: `application/json`

```json
{
  "ok": true,
  "jobId": "string",
  "message": "string"
}
```

**400** – Invalid request or local strategy not supported

Content-Type: `application/json`

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

**401** – Unauthorized

Content-Type: `application/json`

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

**403** – Access denied

Content-Type: `application/json`

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

**404** – Schedule not found

Content-Type: `application/json`

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

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/scheduler/trigger" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "{
  \"id\": \"string\"
}"
```

## DELETE `/storage-providers/s3/delete`

Delete file from S3

**Tags:** S3-Compatible Storage

### Responses

**204** – Success

### Example

```bash
curl -X DELETE "https://open-book-partners-erp.northbound.run/api/storage-providers/s3/delete" \
  -H "Accept: application/json"
```

## GET `/storage-providers/s3/download`

Download file from S3

**Tags:** S3-Compatible Storage

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/storage-providers/s3/download" \
  -H "Accept: application/json"
```

## GET `/storage-providers/s3/list`

List S3 objects

**Tags:** S3-Compatible Storage

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/storage-providers/s3/list" \
  -H "Accept: application/json"
```

## POST `/storage-providers/s3/signed-url`

Generate S3 pre-signed URL

**Tags:** S3-Compatible Storage

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/storage-providers/s3/signed-url" \
  -H "Accept: application/json"
```

## POST `/storage-providers/s3/upload`

Upload file to S3

**Tags:** S3-Compatible Storage

### Responses

**201** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/storage-providers/s3/upload" \
  -H "Accept: application/json"
```

## GET `/sync_quickbooks/connection`

The current organization's QuickBooks connection: company, realm, last error

Requires features: sync_quickbooks.view

**Tags:** QuickBooksSync

**Requires authentication.**

**Features:** sync_quickbooks.view

### Responses

**200** – Connection summary (no secrets)

Content-Type: `application/json`

**400** – No organization selected

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/sync_quickbooks/connection" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/sync_quickbooks/oauth/callback`

QuickBooks OAuth callback — consume state, refuse a company held elsewhere, exchange code, persist tokens

**Tags:** QuickBooksSync

### Responses

**200** – Success response

Content-Type: `application/json`

**302** – Redirect to the QuickBooks integration page, which shows the outcome

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/sync_quickbooks/oauth/callback" \
  -H "Accept: application/json"
```

## POST `/sync_quickbooks/oauth/disconnect`

Disconnect the current organization's QuickBooks company (revoke at Intuit, then forget it here)

Requires features: sync_quickbooks.connect

**Tags:** QuickBooksSync

**Requires authentication.**

**Features:** sync_quickbooks.connect

### Responses

**200** – { disconnected: true, revoke } — revoke is revoked | shared | already_ended | nothing_to_revoke | failed

Content-Type: `application/json`

**400** – No organization selected

Content-Type: `application/json`

**500** – Could not disconnect

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/sync_quickbooks/oauth/disconnect" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## POST `/sync_quickbooks/oauth/initiate`

Start a QuickBooks Online OAuth connect flow

Requires features: sync_quickbooks.connect

**Tags:** QuickBooksSync

**Requires authentication.**

**Features:** sync_quickbooks.connect

### Responses

**200** – Authorize URL returned; a state nonce was persisted

Content-Type: `application/json`

**409** – OAuth client app not configured for this workspace

Content-Type: `application/json`

**500** – Failed to start the connection

Content-Type: `application/json`

### Example

```bash
curl -X POST "https://open-book-partners-erp.northbound.run/api/sync_quickbooks/oauth/initiate" \
  -H "Accept: application/json" \
  -H "authorization: Bearer <token>"
```

## GET `/version`

Deployed Open Mercato version

**Tags:** API Documentation

### Responses

**200** – Success response

Content-Type: `application/json`

### Example

```bash
curl -X GET "https://open-book-partners-erp.northbound.run/api/version" \
  -H "Accept: application/json"
```