notifications

Notifications

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

34 endpoints

GET /api/v1/me/notifications

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

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25
unreadOnly
queryenum: "true" | "false"default: "false"

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "templateKey": "string",
      "locale": "ar",
      "title": "Midterm Exam",
      "body": "string",
      "payload": {},
      "readAt": "2026-03-02T09:00:00.000Z",
      "createdAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200
NameTypeConstraints
okrequired
enum: true—
updatedrequired
integer0–9007199254740991
json
{
  "ok": true,
  "updated": 0
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopeNo additional permission

curl example

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

Responses

200
NameTypeConstraints
okrequired
enum: true—
updatedrequired
integer0–9007199254740991
json
{
  "ok": true,
  "updated": 0
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopeNo additional permission

curl example

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

Responses

200
NameTypeConstraints
unreadrequired
integer0–9007199254740991
json
{
  "unread": 0
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

GET /api/v1/notification-preferences

The caller's own per-channel notification preferences.

Requires authenticationScopeNo additional permission

curl example

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

Responses

200
NameTypeConstraints
preferencesrequired
array<object>—
json
{
  "preferences": [
    {
      "channel": "push",
      "enabled": true,
      "digestMode": "immediate"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

PUT /api/v1/notification-preferences

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

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
preferencesrequired
array<object>—

Example

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

curl example

curl
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"
      }
    ]
  }'

Responses

200
NameTypeConstraints
preferencesrequired
array<object>—
json
{
  "preferences": [
    {
      "channel": "push",
      "enabled": true,
      "digestMode": "immediate"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

Requires authenticationScopenotifications.read

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "typeKey": "string",
      "category": "attendance",
      "trigger": {
        "kind": "event",
        "event": "string",
        "anchor": "string"
      },
      "subject": "string",
      "recipientKinds": [
        "students"
      ],
      "staffPermission": "string",
      "defaultStaffAudience": "none",
      "scopes": [
        "course"
      ],
      "guardianGrouping": "per_child",
      "urgencyChoice": true,
      "locks": {
        "enabled": true,
        "minimumStaff": true,
        "userCanMute": true,
        "emailOn": true
      },
      "reminder": {
        "maxLeads": -9007199254740991,
        "supportsItemMode": true,
        "supportsEnrolmentRelative": true
      },
      "legacySettingKey": "string",
      "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
      },
      "defaults": {
        "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
      },
      "customisedFields": [
        "string"
      ],
      "version": -9007199254740991,
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopenotifications.read

Path & query parameters

NameInTypeConstraints
typeKeyrequired
pathstring—

curl example

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

Responses

200
NameTypeConstraints
typeKeyrequired
string—
categoryrequired
enum: "attendance" | "billing" | "learning" | "reminders" | "institute"—
triggerrequired
object—
subjectrequired
string—
recipientKindsrequired
array<enum: "students" | "guardians" | "teachers" | "staff" | "members">—
staffPermissionrequired
string | null—
defaultStaffAudiencerequired
enum: "none" | "permission_holders" | "owners_managers"—
scopesrequired
array<enum: "course" | "group">—
guardianGroupingrequired
enum: "per_child" | "per_subject"—
urgencyChoicerequired
boolean—
locksrequired
object—
reminderrequired
object | null—
legacySettingKeyrequired
string | null—
rulerequired
object—
defaultsrequired
object—
customisedFieldsrequired
array<string>—
versionrequired
integer-9007199254740991–9007199254740991
updatedAtrequired
string (date-time) | null—
overriderequired
object | null—
channelStatesrequired
array<object>—
json
{
  "typeKey": "string",
  "category": "attendance",
  "trigger": {
    "kind": "event",
    "event": "string",
    "anchor": "string"
  },
  "subject": "string",
  "recipientKinds": [
    "students"
  ],
  "staffPermission": "string",
  "defaultStaffAudience": "none",
  "scopes": [
    "course"
  ],
  "guardianGrouping": "per_child",
  "urgencyChoice": true,
  "locks": {
    "enabled": true,
    "minimumStaff": true,
    "userCanMute": true,
    "emailOn": true
  },
  "reminder": {
    "maxLeads": -9007199254740991,
    "supportsItemMode": true,
    "supportsEnrolmentRelative": true
  },
  "legacySettingKey": "string",
  "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
  },
  "defaults": {
    "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
  },
  "customisedFields": [
    "string"
  ],
  "version": -9007199254740991,
  "updatedAt": "2026-03-02T09:00:00.000Z",
  "override": {
    "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
  },
  "channelStates": [
    {
      "channel": "push",
      "status": "own",
      "reason": "Requested by guardian.",
      "deliverable": true,
      "billing": "free",
      "unitPriceUnits": -9007199254740991
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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`).

Requires authenticationScopenotifications.manage

Path & query parameters

NameInTypeConstraints
typeKeyrequired
pathstring—

Request body

NameTypeConstraints
rulerequired
object—
expectedVersion
integer0–9007199254740991
confirmCost
boolean—

Example

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
}

curl example

curl
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
  }'

Responses

200
NameTypeConstraints
typeKeyrequired
string—
categoryrequired
enum: "attendance" | "billing" | "learning" | "reminders" | "institute"—
triggerrequired
object—
subjectrequired
string—
recipientKindsrequired
array<enum: "students" | "guardians" | "teachers" | "staff" | "members">—
staffPermissionrequired
string | null—
defaultStaffAudiencerequired
enum: "none" | "permission_holders" | "owners_managers"—
scopesrequired
array<enum: "course" | "group">—
guardianGroupingrequired
enum: "per_child" | "per_subject"—
urgencyChoicerequired
boolean—
locksrequired
object—
reminderrequired
object | null—
legacySettingKeyrequired
string | null—
rulerequired
object—
defaultsrequired
object—
customisedFieldsrequired
array<string>—
versionrequired
integer-9007199254740991–9007199254740991
updatedAtrequired
string (date-time) | null—
overriderequired
object | null—
channelStatesrequired
array<object>—
json
{
  "typeKey": "string",
  "category": "attendance",
  "trigger": {
    "kind": "event",
    "event": "string",
    "anchor": "string"
  },
  "subject": "string",
  "recipientKinds": [
    "students"
  ],
  "staffPermission": "string",
  "defaultStaffAudience": "none",
  "scopes": [
    "course"
  ],
  "guardianGrouping": "per_child",
  "urgencyChoice": true,
  "locks": {
    "enabled": true,
    "minimumStaff": true,
    "userCanMute": true,
    "emailOn": true
  },
  "reminder": {
    "maxLeads": -9007199254740991,
    "supportsItemMode": true,
    "supportsEnrolmentRelative": true
  },
  "legacySettingKey": "string",
  "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
  },
  "defaults": {
    "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
  },
  "customisedFields": [
    "string"
  ],
  "version": -9007199254740991,
  "updatedAt": "2026-03-02T09:00:00.000Z",
  "override": {
    "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
  },
  "channelStates": [
    {
      "channel": "push",
      "status": "own",
      "reason": "Requested by guardian.",
      "deliverable": true,
      "billing": "free",
      "unitPriceUnits": -9007199254740991
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopenotifications.read

Path & query parameters

NameInTypeConstraints
typeKeyrequired
pathstring—

Request body

NameTypeConstraints
rule
object—

Example

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

curl example

curl
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
    }
  }'

Responses

200
NameTypeConstraints
effectiverequired
object—
requiresCostConfirmationrequired
boolean—
estimaterequired
object—
recipientsrequired
object—
warningsrequired
array<object>—
json
{
  "effective": {
    "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
  },
  "requiresCostConfirmation": true,
  "estimate": {
    "basis": "history",
    "recipientsPerMonth": -9007199254740991,
    "channels": [
      {
        "channel": "push",
        "billing": "free",
        "messagesLow": -9007199254740991,
        "messagesHigh": -9007199254740991,
        "costLowUnits": -9007199254740991,
        "costHighUnits": -9007199254740991
      }
    ],
    "totalLowUnits": -9007199254740991,
    "totalHighUnits": -9007199254740991
  },
  "recipients": {
    "students": -9007199254740991,
    "guardians": -9007199254740991,
    "teachers": -9007199254740991,
    "staff": -9007199254740991,
    "members": -9007199254740991
  },
  "warnings": [
    {
      "code": "string",
      "detail": "string"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopenotifications.manage

Path & query parameters

NameInTypeConstraints
typeKeyrequired
pathstring—

Request body

NameTypeConstraints
expectedVersion
integer0–9007199254740991
confirmCost
boolean—

Example

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

curl example

curl
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
  }'

Responses

200
NameTypeConstraints
typeKeyrequired
string—
categoryrequired
enum: "attendance" | "billing" | "learning" | "reminders" | "institute"—
triggerrequired
object—
subjectrequired
string—
recipientKindsrequired
array<enum: "students" | "guardians" | "teachers" | "staff" | "members">—
staffPermissionrequired
string | null—
defaultStaffAudiencerequired
enum: "none" | "permission_holders" | "owners_managers"—
scopesrequired
array<enum: "course" | "group">—
guardianGroupingrequired
enum: "per_child" | "per_subject"—
urgencyChoicerequired
boolean—
locksrequired
object—
reminderrequired
object | null—
legacySettingKeyrequired
string | null—
rulerequired
object—
defaultsrequired
object—
customisedFieldsrequired
array<string>—
versionrequired
integer-9007199254740991–9007199254740991
updatedAtrequired
string (date-time) | null—
overriderequired
object | null—
channelStatesrequired
array<object>—
json
{
  "typeKey": "string",
  "category": "attendance",
  "trigger": {
    "kind": "event",
    "event": "string",
    "anchor": "string"
  },
  "subject": "string",
  "recipientKinds": [
    "students"
  ],
  "staffPermission": "string",
  "defaultStaffAudience": "none",
  "scopes": [
    "course"
  ],
  "guardianGrouping": "per_child",
  "urgencyChoice": true,
  "locks": {
    "enabled": true,
    "minimumStaff": true,
    "userCanMute": true,
    "emailOn": true
  },
  "reminder": {
    "maxLeads": -9007199254740991,
    "supportsItemMode": true,
    "supportsEnrolmentRelative": true
  },
  "legacySettingKey": "string",
  "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
  },
  "defaults": {
    "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
  },
  "customisedFields": [
    "string"
  ],
  "version": -9007199254740991,
  "updatedAt": "2026-03-02T09:00:00.000Z",
  "override": {
    "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
  },
  "channelStates": [
    {
      "channel": "push",
      "status": "own",
      "reason": "Requested by guardian.",
      "deliverable": true,
      "billing": "free",
      "unitPriceUnits": -9007199254740991
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopenotifications.manage

curl example

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

Responses

200
NameTypeConstraints
periodrequired
string—
relayrequired
object | null—
usagerequired
array<object>—
json
{
  "period": "string",
  "relay": {
    "balanceUnits": -9007199254740991,
    "overdraftUnits": -9007199254740991,
    "status": "active"
  },
  "usage": [
    {
      "channel": "string",
      "count": -9007199254740991,
      "costMinor": -9007199254740991
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

GET /api/v1/notification-templates

Tenant overrides merged with platform defaults.

Requires authenticationScopenotifications.manage

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "key": "string",
      "channel": "push",
      "locale": "ar",
      "subject": "string",
      "body": "string",
      "providerTemplateName": "string",
      "isActive": true,
      "isPlatformDefault": true,
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

POST /api/v1/notification-templates

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

Requires authenticationScopenotifications.manage

Request body

NameTypeConstraints
keyrequired
string1–150 chars
channelrequired
enum: "push" | "whatsapp" | "email" | "sms" | "in_app"—
localerequired
enum: "ar" | "en" | "he"—
subject
string1–300 chars
bodyrequired
string1–5000 chars
providerTemplateName
string1–150 chars
isActive
booleandefault: true

Example

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

curl example

curl
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
  }'

Responses

201
NameTypeConstraints
idrequired
string (uuid)—
keyrequired
string—
channelrequired
enum: "push" | "whatsapp" | "email" | "sms" | "in_app"—
localerequired
enum: "ar" | "en" | "he"—
subjectrequired
string | null≥1 chars
bodyrequired
string—
providerTemplateNamerequired
string | null≥1 chars
isActiverequired
boolean—
isPlatformDefaultrequired
boolean—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "key": "string",
  "channel": "push",
  "locale": "ar",
  "subject": "string",
  "body": "string",
  "providerTemplateName": "string",
  "isActive": true,
  "isPlatformDefault": true,
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

Deletes a tenant override.

Requires authenticationScopenotifications.manage

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

Requires authenticationScopenotifications.manage

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
subject
string1–300 chars
body
string1–5000 chars
providerTemplateName
string1–150 chars
isActive
booleandefault: true

Example

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

curl example

curl
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
  }'

Responses

200
NameTypeConstraints
idrequired
string (uuid)—
keyrequired
string—
channelrequired
enum: "push" | "whatsapp" | "email" | "sms" | "in_app"—
localerequired
enum: "ar" | "en" | "he"—
subjectrequired
string | null≥1 chars
bodyrequired
string—
providerTemplateNamerequired
string | null≥1 chars
isActiverequired
boolean—
isPlatformDefaultrequired
boolean—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "key": "string",
  "channel": "push",
  "locale": "ar",
  "subject": "string",
  "body": "string",
  "providerTemplateName": "string",
  "isActive": true,
  "isPlatformDefault": true,
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopenotifications.manage

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

201
NameTypeConstraints
okrequired
enum: true—
sentTorequired
string—
channelrequired
enum: "push" | "whatsapp" | "email" | "sms" | "in_app"—
json
{
  "ok": true,
  "sentTo": "string",
  "channel": "push"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopeNo additional permission

curl example

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

Responses

200
NameTypeConstraints
preferencesrequired
array<object>—
json
{
  "preferences": [
    {
      "typeKey": "student.absent",
      "enabled": true,
      "canMute": true
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

PUT /api/v1/notification-type-preferences

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

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
preferencesrequired
array<object>—

Example

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

curl example

curl
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
      }
    ]
  }'

Responses

200
NameTypeConstraints
preferencesrequired
array<object>—
json
{
  "preferences": [
    {
      "typeKey": "student.absent",
      "enabled": true,
      "canMute": true
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

GET /api/v1/notification-usage

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

Requires authenticationScopenotifications.read

curl example

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

Responses

200
NameTypeConstraints
periodrequired
string—
usagerequired
array<object>—
json
{
  "period": "string",
  "usage": [
    {
      "channel": "push",
      "count": -9007199254740991,
      "costMinor": -9007199254740991
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

GET /api/v1/notifications

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

Requires authenticationScopenotifications.read

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25
status
queryenum: "queued" | "sending" | "sent" | "delivered" | "failed" | "skipped"—
channel
queryenum: "push" | "whatsapp" | "email" | "sms" | "in_app"—
recipientUserId
querystring (uuid)—
recipientGuardianId
querystring (uuid)—
recipientStudentId
querystring (uuid)—

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "channel": "push",
      "status": "queued",
      "toAddress": "string",
      "templateKey": "string",
      "locale": "ar",
      "provider": "string",
      "providerMessageId": "wamid.HBgLMTU1NTU1MTIzNDU=",
      "error": "string",
      "costMinor": -9007199254740991,
      "recipientUserId": "86999436-8699-7699-8999-869994368699",
      "recipientGuardianId": "f98e9636-f98e-798e-88e9-f98e9636f98e",
      "recipientStudentId": "69fd1416-69fd-79fd-8fd1-69fd141669fd",
      "scheduledFor": "2026-03-02T09:00:00.000Z",
      "sentAt": "2026-03-02T09:00:00.000Z",
      "deliveredAt": "2026-03-02T09:00:00.000Z",
      "attempts": -9007199254740991,
      "createdAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

Re-queues a failed or skipped notification row.

Requires authenticationScopenotifications.send

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

201
NameTypeConstraints
idrequired
string (uuid)—
channelrequired
enum: "push" | "whatsapp" | "email" | "sms" | "in_app"—
statusrequired
enum: "queued" | "sending" | "sent" | "delivered" | "failed" | "skipped"—
toAddressrequired
string—
templateKeyrequired
string—
localerequired
enum: "ar" | "en" | "he"—
providerrequired
string—
providerMessageIdrequired
string | null≥1 chars
errorrequired
string | null≥1 chars
costMinorrequired
integer | null-9007199254740991–9007199254740991
recipientUserIdrequired
string (uuid) | null—
recipientGuardianIdrequired
string (uuid) | null—
recipientStudentIdrequired
string (uuid) | null—
scheduledForrequired
string (date-time) | null—
sentAtrequired
string (date-time) | null—
deliveredAtrequired
string (date-time) | null—
attemptsrequired
integer-9007199254740991–9007199254740991
createdAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "channel": "push",
  "status": "queued",
  "toAddress": "string",
  "templateKey": "string",
  "locale": "ar",
  "provider": "string",
  "providerMessageId": "wamid.HBgLMTU1NTU1MTIzNDU=",
  "error": "string",
  "costMinor": -9007199254740991,
  "recipientUserId": "86999436-8699-7699-8999-869994368699",
  "recipientGuardianId": "f98e9636-f98e-798e-88e9-f98e9636f98e",
  "recipientStudentId": "69fd1416-69fd-79fd-8fd1-69fd141669fd",
  "scheduledFor": "2026-03-02T09:00:00.000Z",
  "sentAt": "2026-03-02T09:00:00.000Z",
  "deliveredAt": "2026-03-02T09:00:00.000Z",
  "attempts": -9007199254740991,
  "createdAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopenotifications.send

Request body

NameTypeConstraints
templateKey
string1–150 chars
subject
string1–300 chars
body
string1–5000 chars
channels
array<enum: "push" | "whatsapp" | "email" | "sms" | "in_app">—
audiencerequired
object—
payload
objectdefault: {}
urgency
enum: "immediate" | "digest"default: "immediate"
dryRun
booleandefault: false

Example

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
}

curl example

curl
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
  }'

Responses

201
NameTypeConstraints
dryRunrequired
boolean—
recipientCountrequired
integer-9007199254740991–9007199254740991
queuedrequired
integer-9007199254740991–9007199254740991
skippedrequired
integer-9007199254740991–9007199254740991
notificationIdsrequired
array<string (uuid)>—
channelCountsrequired
object—
quotaImpactrequired
object—
json
{
  "dryRun": true,
  "recipientCount": -9007199254740991,
  "queued": -9007199254740991,
  "skipped": -9007199254740991,
  "notificationIds": [
    "6de4da18-6de4-7de4-8e4d-6de4da186de4"
  ],
  "channelCounts": {},
  "quotaImpact": {}
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

GET /api/v1/provider-credentials

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

Requires authenticationScopenotifications.manage

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
json
{
  "data": [
    {
      "channel": "push",
      "provider": "string",
      "isActive": true,
      "verifiedAt": "2026-03-02T09:00:00.000Z"
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

Removes the stored provider credentials for a channel.

Requires authenticationScopenotifications.manage

Path & query parameters

NameInTypeConstraints
channelrequired
pathstring—

curl example

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

Responses

400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopenotifications.manage

Path & query parameters

NameInTypeConstraints
channelrequired
pathstring—

Request body

NameTypeConstraints
providerrequired
string1–50 chars
configrequired
object—

Example

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

curl example

curl
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": {}
  }'

Responses

200
NameTypeConstraints
channelrequired
enum: "push" | "whatsapp" | "email" | "sms" | "in_app"—
providerrequired
string—
isActiverequired
boolean—
verifiedAtrequired
string (date-time) | null—
json
{
  "channel": "push",
  "provider": "string",
  "isActive": true,
  "verifiedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopenotifications.manage

Path & query parameters

NameInTypeConstraints
channelrequired
pathstring—

curl example

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

Responses

201
NameTypeConstraints
okrequired
enum: true—
sentTorequired
string—
json
{
  "ok": true,
  "sentTo": "string"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopeNo additional permission

curl example

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

Responses

200
NameTypeConstraints
datarequired
array<object>—
json
{
  "data": [
    {
      "channel": "push",
      "status": "own",
      "reason": "Requested by guardian."
    }
  ]
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

DELETE /api/v1/push-devices

Unregisters the caller's own push device token.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
tokenrequired
string1–4096 chars

Example

json
{
  "token": "string"
}

curl example

curl
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"
  }'

Responses

200
NameTypeConstraints
okrequired
enum: true—
json
{
  "ok": true
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

POST /api/v1/push-devices

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

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
platformrequired
enum: "ios" | "android" | "web"—
tokenrequired
string1–4096 chars

Example

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

curl example

curl
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"
  }'

Responses

201
NameTypeConstraints
okrequired
enum: true—
json
{
  "ok": true
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopeNo additional permission

curl example

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

Responses

200
NameTypeConstraints
enabledrequired
boolean—
publicKeyrequired
string | null—
json
{
  "enabled": true,
  "publicKey": "string"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

DELETE /api/v1/web-push/subscriptions

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

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
endpointrequired
string0–2048 chars

Example

json
{
  "endpoint": "string"
}

curl example

curl
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"
  }'

Responses

200
NameTypeConstraints
okrequired
enum: true—
json
{
  "ok": true
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
endpointrequired
string0–2048 chars
keysrequired
object—
userAgent
string0–200 chars

Example

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

curl example

curl
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"
  }'

Responses

201
NameTypeConstraints
idrequired
string (uuid)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

402The tenant’s subscription or license is locked (`PAYMENT_REQUIRED`, `SUBSCRIPTION_SUSPENDED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

428An API-key caller must send an `Idempotency-Key` header (`IDEMPOTENCY_KEY_REQUIRED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

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

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

No authenticationScopePublic

Path & query parameters

NameInTypeConstraints
providerrequired
pathstring—

curl example

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

Responses

400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

401Missing, expired or invalid credentials (`UNAUTHORIZED`, `TOKEN_INVALID`, `SESSION_REVOKED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

403Authenticated, but not allowed (`FORBIDDEN`, `FEATURE_DISABLED`, `QUOTA_EXCEEDED`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

404No such resource, or it is not visible to this tenant (`NOT_FOUND`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

409Conflicts with current state (`CONFLICT`, `VERSION_CONFLICT`, `IDEMPOTENCY_KEY_REUSED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

422The request was read but its content is not acceptable: body/query/path/header failed schema validation (`VALIDATION_ERROR`, with `details.issues`), or a domain rule rejected a well-formed value (`VIDEO_URL_INVALID`, …).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

429Rate limit exceeded — see the `RateLimit-*` and `Retry-After` headers (`RATE_LIMITED`).

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.

500Unexpected server error (`INTERNAL_ERROR`). Never carries a stack trace or SQL.

Returns the shared `ErrorEnvelope` — `{ error: { code, message, requestId, details? } }`. Every code is listed in the Conventions guide.