# Notifications

Templates, preferences, send/retry, provider credentials, push device registration, and usage.

Generated from `apps/api/openapi.json`. HTML version: https://docs.institflow.com/en/reference/notifications

34 operations.

## GET /api/v1/me/notifications

The caller’s own in-app notification inbox, newest first (cursor-paginated).

- Operation id: `MeNotificationsController_list_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |
| `unreadOnly` | query | enum: "true" \| "false" | no | default: "false" |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/me/notifications" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/me/notifications/{id}/read

Mark one of the caller’s own inbox entries as read (idempotent).

- Operation id: `MeNotificationsController_markRead_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `ok` | enum: true | yes |  |
| `updated` | integer | yes | 0–9007199254740991 |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/me/notifications/37386ae0-3738-7738-8386-37386ae03738/read" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/me/notifications/read-all

Mark every unread inbox entry of the caller’s own as read.

- Operation id: `MeNotificationsController_markAllRead_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `ok` | enum: true | yes |  |
| `updated` | integer | yes | 0–9007199254740991 |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/me/notifications/read-all" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/me/notifications/unread-count

How many of the caller’s own inbox entries are still unread.

- Operation id: `MeNotificationsController_unreadCount_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `unread` | integer | yes | 0–9007199254740991 |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/me/notifications/unread-count" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/notification-preferences

The caller's own per-channel notification preferences.

- Operation id: `NotificationPreferencesController_get_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `preferences` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/notification-preferences" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PUT /api/v1/notification-preferences

Sets one or more of the caller's own per-channel notification preferences.

- Operation id: `NotificationPreferencesController_put_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `preferences` | array<object> | yes |  |

```json
{
  "preferences": [
    {
      "channel": "push",
      "enabled": true,
      "digestMode": "immediate"
    }
  ]
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `preferences` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/notification-preferences" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "preferences": [
      {
        "channel": "push",
        "enabled": true,
        "digestMode": "immediate"
      }
    ]
  }'
```

## GET /api/v1/notification-rules

The effective rule of every notification type, in the registry order, cursor-paginated (the whole catalogue fits one page). Each entry carries what the type allows, its locks, the Institflow default and the version to echo as `expectedVersion`.

- Operation id: `NotificationRulesController_list_v1`
- Permission: `notifications.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/notification-rules" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/notification-rules/{typeKey}

One type: the effective rule, the institute's own override, the locks and — per channel — whether it can deliver, who bills it and at what price.

- Operation id: `NotificationRulesController_get_v1`
- Permission: `notifications.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `typeKey` | path | string | yes |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `typeKey` | string | yes |  |
| `category` | enum: "attendance" \| "billing" \| "learning" \| "reminders" \| "institute" | yes |  |
| `trigger` | object | yes |  |
| `subject` | string | yes |  |
| `recipientKinds` | array<enum: "students" \| "guardians" \| "teachers" \| "staff" \| "members"> | yes |  |
| `staffPermission` | string \| null | yes |  |
| `defaultStaffAudience` | enum: "none" \| "permission_holders" \| "owners_managers" | yes |  |
| `scopes` | array<enum: "course" \| "group"> | yes |  |
| `guardianGrouping` | enum: "per_child" \| "per_subject" | yes |  |
| `urgencyChoice` | boolean | yes |  |
| `locks` | object | yes |  |
| `reminder` | object \| null | yes |  |
| `legacySettingKey` | string \| null | yes |  |
| `rule` | object | yes |  |
| `defaults` | object | yes |  |
| `customisedFields` | array<string> | yes |  |
| `version` | integer | yes | -9007199254740991–9007199254740991 |
| `updatedAt` | string (date-time) \| null | yes |  |
| `override` | object \| null | yes |  |
| `channelStates` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/notification-rules/string" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PUT /api/v1/notification-rules/{typeKey}

Replaces the institute's override of one type (a field left out is the Institflow default again). `expectedVersion` guards against a concurrent edit (409 `VERSION_CONFLICT`). A change that raises what the institute pays for — a paid channel switched on, recipients, lead times or scope added to a type with a paid channel — is refused with 422 `VALIDATION_ERROR` and `details.reason = "cost_confirmation_required"` plus the estimate, unless `confirmCost` is `true`. A channel this install cannot deliver cannot be switched on (403 `FEATURE_DISABLED`).

- Operation id: `NotificationRulesController_put_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `typeKey` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `rule` | object | yes |  |
| `expectedVersion` | integer | no | 0–9007199254740991 |
| `confirmCost` | boolean | no |  |

```json
{
  "rule": {
    "enabled": true,
    "students": true,
    "teachers": true,
    "guardians": "none",
    "staff": {
      "mode": "default",
      "roleIds": [
        "9ded041d-9ded-7ded-8ed0-9ded041d9ded"
      ]
    },
    "memberIds": [
      "b34e3ee7-b34e-734e-84e3-b34e3ee7b34e"
    ],
    "channels": {
      "push": true,
      "email": "ada.lovelace@example.com",
      "sms": true,
      "whatsapp": true
    },
    "urgency": "immediate",
    "reminder": {
      "leads": [
        "value"
      ],
      "respectQuietHours": true,
      "includeEnrolmentRelative": true
    },
    "scope": {
      "mode": "all",
      "courseIds": [
        "8e692f22-8e69-7e69-8692-8e692f228e69"
      ],
      "groupIds": [
        "b0a20d96-b0a2-70a2-8a20-b0a20d96b0a2"
      ]
    },
    "userCanMute": true
  },
  "expectedVersion": 0,
  "confirmCost": true
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `typeKey` | string | yes |  |
| `category` | enum: "attendance" \| "billing" \| "learning" \| "reminders" \| "institute" | yes |  |
| `trigger` | object | yes |  |
| `subject` | string | yes |  |
| `recipientKinds` | array<enum: "students" \| "guardians" \| "teachers" \| "staff" \| "members"> | yes |  |
| `staffPermission` | string \| null | yes |  |
| `defaultStaffAudience` | enum: "none" \| "permission_holders" \| "owners_managers" | yes |  |
| `scopes` | array<enum: "course" \| "group"> | yes |  |
| `guardianGrouping` | enum: "per_child" \| "per_subject" | yes |  |
| `urgencyChoice` | boolean | yes |  |
| `locks` | object | yes |  |
| `reminder` | object \| null | yes |  |
| `legacySettingKey` | string \| null | yes |  |
| `rule` | object | yes |  |
| `defaults` | object | yes |  |
| `customisedFields` | array<string> | yes |  |
| `version` | integer | yes | -9007199254740991–9007199254740991 |
| `updatedAt` | string (date-time) \| null | yes |  |
| `override` | object \| null | yes |  |
| `channelStates` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/notification-rules/string" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "rule": {
      "enabled": true,
      "students": true,
      "teachers": true,
      "guardians": "none",
      "staff": {
        "mode": "default",
        "roleIds": [
          "9ded041d-9ded-7ded-8ed0-9ded041d9ded"
        ]
      },
      "memberIds": [
        "b34e3ee7-b34e-734e-84e3-b34e3ee7b34e"
      ],
      "channels": {
        "push": true,
        "email": "ada.lovelace@example.com",
        "sms": true,
        "whatsapp": true
      },
      "urgency": "immediate",
      "reminder": {
        "leads": [
          "value"
        ],
        "respectQuietHours": true,
        "includeEnrolmentRelative": true
      },
      "scope": {
        "mode": "all",
        "courseIds": [
          "8e692f22-8e69-7e69-8692-8e692f228e69"
        ],
        "groupIds": [
          "b0a20d96-b0a2-70a2-8a20-b0a20d96b0a2"
        ]
      },
      "userCanMute": true
    },
    "expectedVersion": 0,
    "confirmCost": true
  }'
```

## POST /api/v1/notification-rules/{typeKey}/preview

Changes nothing: the monthly cost estimate (a range, in credit units), how many people the rule would reach by kind, whether saving it needs `confirmCost`, and warnings. Without a body it describes the current rule.

- Operation id: `NotificationRulesController_preview_v1`
- Permission: `notifications.read`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `typeKey` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `rule` | object | no |  |

```json
{
  "rule": {
    "enabled": true,
    "students": true,
    "teachers": true,
    "guardians": "none",
    "staff": {
      "mode": "default",
      "roleIds": [
        "9ded041d-9ded-7ded-8ed0-9ded041d9ded"
      ]
    },
    "memberIds": [
      "b34e3ee7-b34e-734e-84e3-b34e3ee7b34e"
    ],
    "channels": {
      "push": true,
      "email": "ada.lovelace@example.com",
      "sms": true,
      "whatsapp": true
    },
    "urgency": "immediate",
    "reminder": {
      "leads": [
        "value"
      ],
      "respectQuietHours": true,
      "includeEnrolmentRelative": true
    },
    "scope": {
      "mode": "all",
      "courseIds": [
        "8e692f22-8e69-7e69-8692-8e692f228e69"
      ],
      "groupIds": [
        "b0a20d96-b0a2-70a2-8a20-b0a20d96b0a2"
      ]
    },
    "userCanMute": true
  }
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `effective` | object | yes |  |
| `requiresCostConfirmation` | boolean | yes |  |
| `estimate` | object | yes |  |
| `recipients` | object | yes |  |
| `warnings` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/notification-rules/string/preview" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "rule": {
      "enabled": true,
      "students": true,
      "teachers": true,
      "guardians": "none",
      "staff": {
        "mode": "default",
        "roleIds": [
          "9ded041d-9ded-7ded-8ed0-9ded041d9ded"
        ]
      },
      "memberIds": [
        "b34e3ee7-b34e-734e-84e3-b34e3ee7b34e"
      ],
      "channels": {
        "push": true,
        "email": "ada.lovelace@example.com",
        "sms": true,
        "whatsapp": true
      },
      "urgency": "immediate",
      "reminder": {
        "leads": [
          "value"
        ],
        "respectQuietHours": true,
        "includeEnrolmentRelative": true
      },
      "scope": {
        "mode": "all",
        "courseIds": [
          "8e692f22-8e69-7e69-8692-8e692f228e69"
        ],
        "groupIds": [
          "b0a20d96-b0a2-70a2-8a20-b0a20d96b0a2"
        ]
      },
      "userCanMute": true
    }
  }'
```

## POST /api/v1/notification-rules/{typeKey}/reset

Deletes the override the institute made: the type goes back to the Institflow default. Going back can switch the type or a paid channel on again; that answers 422 with the estimate until the request carries `confirmCost: true`, exactly like a save.

- Operation id: `NotificationRulesController_reset_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `typeKey` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `expectedVersion` | integer | no | 0–9007199254740991 |
| `confirmCost` | boolean | no |  |

```json
{
  "expectedVersion": 0,
  "confirmCost": true
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `typeKey` | string | yes |  |
| `category` | enum: "attendance" \| "billing" \| "learning" \| "reminders" \| "institute" | yes |  |
| `trigger` | object | yes |  |
| `subject` | string | yes |  |
| `recipientKinds` | array<enum: "students" \| "guardians" \| "teachers" \| "staff" \| "members"> | yes |  |
| `staffPermission` | string \| null | yes |  |
| `defaultStaffAudience` | enum: "none" \| "permission_holders" \| "owners_managers" | yes |  |
| `scopes` | array<enum: "course" \| "group"> | yes |  |
| `guardianGrouping` | enum: "per_child" \| "per_subject" | yes |  |
| `urgencyChoice` | boolean | yes |  |
| `locks` | object | yes |  |
| `reminder` | object \| null | yes |  |
| `legacySettingKey` | string \| null | yes |  |
| `rule` | object | yes |  |
| `defaults` | object | yes |  |
| `customisedFields` | array<string> | yes |  |
| `version` | integer | yes | -9007199254740991–9007199254740991 |
| `updatedAt` | string (date-time) \| null | yes |  |
| `override` | object \| null | yes |  |
| `channelStates` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/notification-rules/string/reset" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "expectedVersion": 0,
    "confirmCost": true
  }'
```

## GET /api/v1/notification-spend

This month's notification usage per channel and, when the install is on the Institflow messaging relay, the credit balance and overdraft limit as last synced.

- Operation id: `NotificationSpendController_get_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: not required

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `period` | string | yes |  |
| `relay` | object \| null | yes |  |
| `usage` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/notification-spend" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/notification-templates

Tenant overrides merged with platform defaults.

- Operation id: `NotificationTemplatesController_list_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/notification-templates" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/notification-templates

Creates a tenant override for a template key/channel/locale.

- Operation id: `NotificationTemplatesController_create_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `key` | string | yes | 1–150 chars |
| `channel` | enum: "push" \| "whatsapp" \| "email" \| "sms" \| "in_app" | yes |  |
| `locale` | enum: "ar" \| "en" \| "he" | yes |  |
| `subject` | string | no | 1–300 chars |
| `body` | string | yes | 1–5000 chars |
| `providerTemplateName` | string | no | 1–150 chars |
| `isActive` | boolean | no | default: true |

```json
{
  "key": "string",
  "channel": "push",
  "locale": "ar",
  "subject": "string",
  "body": "string",
  "providerTemplateName": "string",
  "isActive": true
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `key` | string | yes |  |
| `channel` | enum: "push" \| "whatsapp" \| "email" \| "sms" \| "in_app" | yes |  |
| `locale` | enum: "ar" \| "en" \| "he" | yes |  |
| `subject` | string \| null | yes | ≥1 chars |
| `body` | string | yes |  |
| `providerTemplateName` | string \| null | yes | ≥1 chars |
| `isActive` | boolean | yes |  |
| `isPlatformDefault` | boolean | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/notification-templates" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "key": "string",
    "channel": "push",
    "locale": "ar",
    "subject": "string",
    "body": "string",
    "providerTemplateName": "string",
    "isActive": true
  }'
```

## DELETE /api/v1/notification-templates/{id}

Deletes a tenant override.

- Operation id: `NotificationTemplatesController_remove_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X DELETE "https://your-institute.example/api/v1/notification-templates/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PATCH /api/v1/notification-templates/{id}

Updates a tenant override (never a platform default row).

- Operation id: `NotificationTemplatesController_update_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `subject` | string | no | 1–300 chars |
| `body` | string | no | 1–5000 chars |
| `providerTemplateName` | string | no | 1–150 chars |
| `isActive` | boolean | no | default: true |

```json
{
  "subject": "string",
  "body": "string",
  "providerTemplateName": "string",
  "isActive": true
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `key` | string | yes |  |
| `channel` | enum: "push" \| "whatsapp" \| "email" \| "sms" \| "in_app" | yes |  |
| `locale` | enum: "ar" \| "en" \| "he" | yes |  |
| `subject` | string \| null | yes | ≥1 chars |
| `body` | string | yes |  |
| `providerTemplateName` | string \| null | yes | ≥1 chars |
| `isActive` | boolean | yes |  |
| `isPlatformDefault` | boolean | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PATCH "https://your-institute.example/api/v1/notification-templates/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "subject": "string",
    "body": "string",
    "providerTemplateName": "string",
    "isActive": true
  }'
```

## POST /api/v1/notification-templates/{id}/test

Renders this template against a fixed sample payload and sends it, through the tenant's real provider for its channel, to the calling user's own contact address. Never sends to anyone else.

- Operation id: `NotificationTemplatesController_test_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `ok` | enum: true | yes |  |
| `sentTo` | string | yes |  |
| `channel` | enum: "push" \| "whatsapp" \| "email" \| "sms" \| "in_app" | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/notification-templates/37386ae0-3738-7738-8386-37386ae03738/test" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/notification-type-preferences

The caller's own per-type notification switches (class reminder, work-due reminder, new lesson). A type never switched off is on.

- Operation id: `NotificationTypePreferencesController_get_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `preferences` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/notification-type-preferences" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PUT /api/v1/notification-type-preferences

Switches one or more of the caller's own notification types on or off.

- Operation id: `NotificationTypePreferencesController_put_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `preferences` | array<object> | yes |  |

```json
{
  "preferences": [
    {
      "typeKey": "student.absent",
      "enabled": true
    }
  ]
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `preferences` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/notification-type-preferences" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "preferences": [
      {
        "typeKey": "student.absent",
        "enabled": true
      }
    ]
  }'
```

## GET /api/v1/notification-usage

Per-channel usage/cost counters for a billing period (default: the current month).

- Operation id: `NotificationUsageController_getUsage_v1`
- Permission: `notifications.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `period` | string | yes |  |
| `usage` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/notification-usage" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/notifications

List the outbox log, cursor-paginated, filterable by status/channel/recipient.

- Operation id: `NotificationsController_list_v1`
- Permission: `notifications.read`
- Auth: credential required
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `cursor` | query | string | no |  |
| `limit` | query | integer | no | 1–100 · default: 25 |
| `status` | query | enum: "queued" \| "sending" \| "sent" \| "delivered" \| "failed" \| "skipped" | no |  |
| `channel` | query | enum: "push" \| "whatsapp" \| "email" \| "sms" \| "in_app" | no |  |
| `recipientUserId` | query | string (uuid) | no |  |
| `recipientGuardianId` | query | string (uuid) | no |  |
| `recipientStudentId` | query | string (uuid) | no |  |

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |
| `nextCursor` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/notifications" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/notifications/{id}/retry

Re-queues a failed or skipped notification row.

- Operation id: `NotificationsController_retry_v1`
- Permission: `notifications.send`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `channel` | enum: "push" \| "whatsapp" \| "email" \| "sms" \| "in_app" | yes |  |
| `status` | enum: "queued" \| "sending" \| "sent" \| "delivered" \| "failed" \| "skipped" | yes |  |
| `toAddress` | string | yes |  |
| `templateKey` | string | yes |  |
| `locale` | enum: "ar" \| "en" \| "he" | yes |  |
| `provider` | string | yes |  |
| `providerMessageId` | string \| null | yes | ≥1 chars |
| `error` | string \| null | yes | ≥1 chars |
| `costMinor` | integer \| null | yes | -9007199254740991–9007199254740991 |
| `recipientUserId` | string (uuid) \| null | yes |  |
| `recipientGuardianId` | string (uuid) \| null | yes |  |
| `recipientStudentId` | string (uuid) \| null | yes |  |
| `scheduledFor` | string (date-time) \| null | yes |  |
| `sentAt` | string (date-time) \| null | yes |  |
| `deliveredAt` | string (date-time) \| null | yes |  |
| `attempts` | integer | yes | -9007199254740991–9007199254740991 |
| `createdAt` | string (date-time) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/notifications/37386ae0-3738-7738-8386-37386ae03738/retry" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## POST /api/v1/notifications/send

Manual broadcast to a group/branch/explicit student list. `dryRun` returns recipient count + quota impact without sending. Naming `channels` explicitly with `sms`/`whatsapp` this tenant is not approved for (`sms_notifications`/`whatsapp_notifications` not effective) is rejected with 422 `VALIDATION_ERROR` rather than silently sending a smaller broadcast; leaving `channels` unset falls through the tenant ladder instead, which skips an unapproved channel silently and still reaches the in-app inbox.

- Operation id: `NotificationsController_send_v1`
- Permission: `notifications.send`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `templateKey` | string | no | 1–150 chars |
| `subject` | string | no | 1–300 chars |
| `body` | string | no | 1–5000 chars |
| `channels` | array<enum: "push" \| "whatsapp" \| "email" \| "sms" \| "in_app"> | no |  |
| `audience` | object | yes |  |
| `payload` | object | no | default: {} |
| `urgency` | enum: "immediate" \| "digest" | no | default: "immediate" |
| `dryRun` | boolean | no | default: false |

```json
{
  "templateKey": "string",
  "subject": "string",
  "body": "string",
  "channels": [
    "push"
  ],
  "audience": {
    "groupId": "6dc7fce6-6dc7-7dc7-8c7f-6dc7fce66dc7",
    "branchId": "390b8975-390b-790b-80b8-390b8975390b",
    "studentIds": [
      "af05d28a-af05-7f05-805d-af05d28aaf05"
    ],
    "recipients": "both"
  },
  "payload": {},
  "urgency": "immediate",
  "dryRun": false
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `dryRun` | boolean | yes |  |
| `recipientCount` | integer | yes | -9007199254740991–9007199254740991 |
| `queued` | integer | yes | -9007199254740991–9007199254740991 |
| `skipped` | integer | yes | -9007199254740991–9007199254740991 |
| `notificationIds` | array<string (uuid)> | yes |  |
| `channelCounts` | object | yes |  |
| `quotaImpact` | object | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/notifications/send" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "templateKey": "string",
    "subject": "string",
    "body": "string",
    "channels": [
      "push"
    ],
    "audience": {
      "groupId": "6dc7fce6-6dc7-7dc7-8c7f-6dc7fce66dc7",
      "branchId": "390b8975-390b-790b-80b8-390b8975390b",
      "studentIds": [
        "af05d28a-af05-7f05-805d-af05d28aaf05"
      ],
      "recipients": "both"
    },
    "payload": {},
    "urgency": "immediate",
    "dryRun": false
  }'
```

## GET /api/v1/provider-credentials

Lists configured provider credentials — provider id + verified_at only, never secrets.

- Operation id: `ProviderCredentialsController_list_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: not required

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/provider-credentials" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## DELETE /api/v1/provider-credentials/{channel}

Removes the stored provider credentials for a channel.

- Operation id: `ProviderCredentialsController_remove_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `channel` | path | string | yes |  |

### Responses

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X DELETE "https://your-institute.example/api/v1/provider-credentials/string" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## PUT /api/v1/provider-credentials/{channel}

Sets (envelope-encrypted) provider credentials for a channel. Never echoes the secret back. 403 FEATURE_DISABLED for `sms`/`whatsapp` (platform/licence-approved per tenant, no platform sender exists for either in 1.0.0) or for `email`/`push` when the matching `byo_email_provider`/`byo_push_provider` flag is not granted — the platform SMTP/Expo sender keeps working either way.

- Operation id: `ProviderCredentialsController_put_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `channel` | path | string | yes |  |

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `provider` | string | yes | 1–50 chars |
| `config` | object | yes |  |

```json
{
  "provider": "string",
  "config": {}
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `channel` | enum: "push" \| "whatsapp" \| "email" \| "sms" \| "in_app" | yes |  |
| `provider` | string | yes |  |
| `isActive` | boolean | yes |  |
| `verifiedAt` | string (date-time) \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X PUT "https://your-institute.example/api/v1/provider-credentials/string" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "provider": "string",
    "config": {}
  }'
```

## POST /api/v1/provider-credentials/{channel}/test

Sends a real test message on this channel to the caller's own contact address. Same 403 FEATURE_DISABLED gating as PUT applies here — testing a channel this tenant has not been approved for (or, for email/push, a stored override without byo_email_provider/byo_push_provider) is rejected before anything is sent.

- Operation id: `ProviderCredentialsController_test_v1`
- Permission: `notifications.manage`
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `channel` | path | string | yes |  |

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `ok` | enum: true | yes |  |
| `sentTo` | string | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/provider-credentials/string/test" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## GET /api/v1/provider-credentials/status

Per-channel deliverability for this tenant right now — 'own' (approved tenant credentials active), 'platform' (no tenant override, but email/push/in_app always send through Institflow's own sender), or 'unavailable' (sms/whatsapp with no approved, configured tenant credential — the only case nothing sends). No permission required and carries no secret: every screen that plans a send (the broadcast wizard, the invoices reminder banner, the onboarding checklist) reads this instead of guessing from the credentials list.

- Operation id: `ProviderCredentialsController_status_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `data` | array<object> | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/provider-credentials/status" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## DELETE /api/v1/push-devices

Unregisters the caller's own push device token.

- Operation id: `PushDevicesController_unregister_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `token` | string | yes | 1–4096 chars |

```json
{
  "token": "string"
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `ok` | enum: true | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X DELETE "https://your-institute.example/api/v1/push-devices" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "token": "string"
  }'
```

## POST /api/v1/push-devices

Registers (or refreshes) the caller's own push device token.

- Operation id: `PushDevicesController_register_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `platform` | enum: "ios" \| "android" \| "web" | yes |  |
| `token` | string | yes | 1–4096 chars |

```json
{
  "platform": "ios",
  "token": "string"
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `ok` | enum: true | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/push-devices" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "platform": "ios",
    "token": "string"
  }'
```

## GET /api/v1/web-push/config

Whether this server can send browser push and, if so, the public key to subscribe with. `enabled: false` means the page should offer no browser-notification controls.

- Operation id: `WebPushController_config_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: not required

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `enabled` | boolean | yes |  |
| `publicKey` | string \| null | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X GET "https://your-institute.example/api/v1/web-push/config" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```

## DELETE /api/v1/web-push/subscriptions

Removes the caller's own browser push subscription for an endpoint (idempotent).

- Operation id: `WebPushController_unsubscribe_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `endpoint` | string | yes | 0–2048 chars |

```json
{
  "endpoint": "string"
}
```

### Responses

**200** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `ok` | enum: true | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X DELETE "https://your-institute.example/api/v1/web-push/subscriptions" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "endpoint": "string"
  }'
```

## POST /api/v1/web-push/subscriptions

Registers (or refreshes) the caller's browser push subscription (`PushSubscription.toJSON()`). Refused with a feature-disabled error while the server has no push transport.

- Operation id: `WebPushController_subscribe_v1`
- Permission: —
- Auth: credential required
- `Idempotency-Key`: required for API-key callers

### Request body

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `endpoint` | string | yes | 0–2048 chars |
| `keys` | object | yes |  |
| `userAgent` | string | no | 0–200 chars |

```json
{
  "endpoint": "string",
  "keys": {
    "p256dh": "string",
    "auth": "string"
  },
  "userAgent": "string"
}
```

### Responses

**201** — Success.

| Property | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/web-push/subscriptions" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "endpoint": "string",
    "keys": {
      "p256dh": "string",
      "auth": "string"
    },
    "userAgent": "string"
  }'
```

## POST /api/v1/webhooks/providers/{provider}

Inbound delivery-status webhook from a notification provider (Meta/Twilio). Signature-verified.

- Operation id: `NotificationWebhooksController_handle_v1`
- Permission: —
- Auth: public (no credential)
- `Idempotency-Key`: not required

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `provider` | path | string | yes |  |

### Responses

Every other status answers with the shared `ErrorEnvelope` — see
https://docs.institflow.com/md/en/error-codes.md

### Example request

```bash
curl -X POST "https://your-institute.example/api/v1/webhooks/providers/string" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"
```
