platform

Platform Admin

Cross-tenant platform administration: tenants, plans, licenses, custom domains, subscriptions, platform users, and job queues. Requires a platform-admin account, not a tenant membership.

116 endpoints

Remove a student/guardian institute link. Idempotent — already-removed is returned as-is.

No authenticationScopePublic

Request body

NameTypeConstraints
kindrequired
enum: "student" | "guardian"—
emailrequired
string (email)0–320 chars

Example

json
{
  "kind": "student",
  "email": "ada.lovelace@example.com"
}

curl example

curl
curl -X DELETE "https://your-institute.example/api/v1/platform/accounts/links" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "kind": "student",
    "email": "ada.lovelace@example.com"
  }'

Responses

200The removed link.
NameTypeConstraints
idrequired
string (uuid)—
kindrequired
enum: "student" | "guardian"—
statusrequired
enum: "invited" | "active" | "removed" | "declined"—
invitedAtrequired
string (date-time) | null—
activatedAtrequired
string (date-time) | null—
removedAtrequired
string (date-time) | null—
declinedAtrequired
string (date-time) | null—
inviteEmailSentAtrequired
string (date-time) | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "kind": "student",
  "status": "invited",
  "invitedAt": "2026-03-02T09:00:00.000Z",
  "activatedAt": "2026-03-02T09:00:00.000Z",
  "removedAt": "2026-03-02T09:00:00.000Z",
  "declinedAt": "2026-03-02T09:00:00.000Z",
  "inviteEmailSentAt": "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.

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.

This install's own institute links, newest first — never another install's or the account's other institutes.

No authenticationScopePublic

Path & query parameters

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

curl example

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

Responses

200A page of institute links.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "kind": "student",
      "status": "invited",
      "invitedAt": "2026-03-02T09:00:00.000Z",
      "activatedAt": "2026-03-02T09:00:00.000Z",
      "removedAt": "2026-03-02T09:00:00.000Z",
      "declinedAt": "2026-03-02T09:00:00.000Z",
      "inviteEmailSentAt": "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.

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/platform/accounts/links

Invite a student/guardian by email to this institute. Idempotent on the link — a repeat returns the existing link; the invitation email is sent again only if it was never delivered, or when `resend` is set and the last send is at least two minutes old.

No authenticationScopePublic

Request body

NameTypeConstraints
kindrequired
enum: "student" | "guardian"—
email
string (email)0–320 chars
identifier
object | object—
resend
boolean—
ref
string (uuid)—

Example

json
{
  "kind": "student",
  "email": "ada.lovelace@example.com",
  "identifier": {
    "email": "ada.lovelace@example.com"
  },
  "resend": true,
  "ref": "42f48402-42f4-72f4-8f48-42f4840242f4"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/accounts/links" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "kind": "student",
    "email": "ada.lovelace@example.com",
    "identifier": {
      "email": "ada.lovelace@example.com"
    },
    "resend": true,
    "ref": "42f48402-42f4-72f4-8f48-42f4840242f4"
  }'

Responses

201The invited (or already-invited) link.
NameTypeConstraints
idrequired
string (uuid)—
kindrequired
enum: "student" | "guardian"—
statusrequired
enum: "invited" | "active" | "removed" | "declined"—
invitedAtrequired
string (date-time) | null—
activatedAtrequired
string (date-time) | null—
removedAtrequired
string (date-time) | null—
declinedAtrequired
string (date-time) | null—
inviteEmailSentAtrequired
string (date-time) | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "kind": "student",
  "status": "invited",
  "invitedAt": "2026-03-02T09:00:00.000Z",
  "activatedAt": "2026-03-02T09:00:00.000Z",
  "removedAt": "2026-03-02T09:00:00.000Z",
  "declinedAt": "2026-03-02T09:00:00.000Z",
  "inviteEmailSentAt": "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.

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/platform/accounts/links/activate

The first successful Institflow-account sign-in into this institute (§2.5 step 3). Idempotent — activating an already-active link is a no-op.

No authenticationScopePublic

Request body

NameTypeConstraints
kindrequired
enum: "student" | "guardian"—
emailrequired
string (email)0–320 chars
accountIdrequired
string (uuid)—
ensure
boolean—

Example

json
{
  "kind": "student",
  "email": "ada.lovelace@example.com",
  "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
  "ensure": true
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/accounts/links/activate" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "kind": "student",
    "email": "ada.lovelace@example.com",
    "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
    "ensure": true
  }'

Responses

200The activated link.
NameTypeConstraints
idrequired
string (uuid)—
kindrequired
enum: "student" | "guardian"—
statusrequired
enum: "invited" | "active" | "removed" | "declined"—
invitedAtrequired
string (date-time) | null—
activatedAtrequired
string (date-time) | null—
removedAtrequired
string (date-time) | null—
declinedAtrequired
string (date-time) | null—
inviteEmailSentAtrequired
string (date-time) | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "kind": "student",
  "status": "invited",
  "invitedAt": "2026-03-02T09:00:00.000Z",
  "activatedAt": "2026-03-02T09:00:00.000Z",
  "removedAt": "2026-03-02T09:00:00.000Z",
  "declinedAt": "2026-03-02T09:00:00.000Z",
  "inviteEmailSentAt": "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.

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/platform/accounts/links/decision

Tell the account that asked to join through a join link the owner's answer (`approved` or `rejected`): it is mailed. The account must own the `email` given.

No authenticationScopePublic

Request body

NameTypeConstraints
kindrequired
enum: "student" | "guardian"—
emailrequired
string (email)0–320 chars
accountIdrequired
string (uuid)—
decisionrequired
enum: "approved" | "rejected"—

Example

json
{
  "kind": "student",
  "email": "ada.lovelace@example.com",
  "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
  "decision": "approved"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/accounts/links/decision" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "kind": "student",
    "email": "ada.lovelace@example.com",
    "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
    "decision": "approved"
  }'

Responses

200The account was notified.
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.

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/platform/accounts/links/responses

The answer to each of this install's own invitations (`ref` = the install's invitation id): `pending`, `accepted` or `declined`. A ref this install never made reads `pending`, like an open one.

No authenticationScopePublic

Request body

NameTypeConstraints
refsrequired
array<string (uuid)>—

Example

json
{
  "refs": [
    "d7ec7de3-d7ec-77ec-8ec7-d7ec7de3d7ec"
  ]
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/accounts/links/responses" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "refs": [
      "d7ec7de3-d7ec-77ec-8ec7-d7ec7de3d7ec"
    ]
  }'

Responses

200One entry per requested ref.
NameTypeConstraints
datarequired
array<object>—
json
{
  "data": [
    {
      "ref": "42f48402-42f4-72f4-8f48-42f4840242f4",
      "response": "pending"
    }
  ]
}
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.

GET /api/v1/platform/admins

List platform admins: role, status, 2FA enrolment, last login.

Requires authenticationScopeNo additional permission

Path & query parameters

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

curl example

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

Responses

200A page of platform admins.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "userId": "11f967df-11f9-71f9-8f96-11f967df11f9",
      "email": "ada.lovelace@example.com",
      "fullName": "Ada Lovelace",
      "role": "owner",
      "status": "active",
      "totpEnrolled": true,
      "lastLoginAt": "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.

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/platform/admins/{userId}

Remove a platform admin (the user account itself is untouched). Owner only, cannot target yourself, cannot remove the last enabled owner.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
userIdrequired
pathstring—

curl example

curl
curl -X DELETE "https://your-institute.example/api/v1/platform/admins/11f967df-11f9-71f9-8f96-11f967df11f9" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The platform admin was removed.
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.

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.

PATCH /api/v1/platform/admins/{userId}

Change a platform admin’s role. Owner only, cannot target yourself; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
userIdrequired
pathstring—

Request body

NameTypeConstraints
rolerequired
enum: "owner" | "support"—

Example

json
{
  "role": "owner"
}

curl example

curl
curl -X PATCH "https://your-institute.example/api/v1/platform/admins/11f967df-11f9-71f9-8f96-11f967df11f9" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "role": "owner"
  }'

Responses

200The role was changed.
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.

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/platform/admins/{userId}/disable

Disable a platform admin (revokes every session). Owner only, cannot target yourself, cannot disable the last enabled owner.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
userIdrequired
pathstring—

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/admins/11f967df-11f9-71f9-8f96-11f967df11f9/disable" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The platform admin was disabled.
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.

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/platform/admins/{userId}/enable

Re-enable a disabled platform admin. Owner only; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
userIdrequired
pathstring—

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/admins/11f967df-11f9-71f9-8f96-11f967df11f9/enable" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The platform admin was enabled.
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.

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/platform/admins/{userId}/totp/reset

Reset a platform admin’s two-factor authentication for a lost device — they enrol again at next login. Owner only.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
userIdrequired
pathstring—

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/admins/11f967df-11f9-71f9-8f96-11f967df11f9/totp/reset" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200Two-factor authentication was reset.
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.

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/platform/admins/invitations

List every platform-admin invitation, pending or not.

Requires authenticationScopeNo additional permission

Path & query parameters

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

curl example

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

Responses

200A page of invitations.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "email": "ada.lovelace@example.com",
      "role": "owner",
      "invitedBy": "27215cb9-2721-7721-8215-27215cb92721",
      "expiresAt": "2026-03-02T09:00:00.000Z",
      "acceptedAt": "2026-03-02T09:00:00.000Z",
      "revokedAt": "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.

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/platform/admins/invitations

Invite a new platform admin by email. Owner only; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
emailrequired
string (email)—
rolerequired
enum: "owner" | "support"—

Example

json
{
  "email": "ada.lovelace@example.com",
  "role": "owner"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/admins/invitations" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "email": "ada.lovelace@example.com",
    "role": "owner"
  }'

Responses

201The created invitation.
NameTypeConstraints
idrequired
string (uuid)—
emailrequired
string (email)—
rolerequired
enum: "owner" | "support"—
invitedByrequired
string (uuid)—
expiresAtrequired
string (date-time)—
acceptedAtrequired
string (date-time) | null—
revokedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "email": "ada.lovelace@example.com",
  "role": "owner",
  "invitedBy": "27215cb9-2721-7721-8215-27215cb92721",
  "expiresAt": "2026-03-02T09:00:00.000Z",
  "acceptedAt": "2026-03-02T09:00:00.000Z",
  "revokedAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/admins/invitations/{id}/revoke

Revoke a still-pending platform-admin invitation. Owner only; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The invitation was revoked.
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.

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/platform/customer-requests

List institute requests (cursor-paginated, optional status filter).

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25
status
queryenum: "pending" | "approved" | "refused"—

curl example

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

Responses

200A page of institute requests.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "instituteName": "string",
      "slug": "string",
      "country": "string",
      "sizeBand": "small",
      "phone": "+15551234567",
      "status": "pending",
      "refusalReason": "string",
      "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z",
      "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
      "requesterEmail": "ada.lovelace@example.com",
      "decidedAt": "2026-03-02T09:00:00.000Z",
      "decidedBy": "d4d316ba-d4d3-74d3-8d31-d4d316bad4d3"
    }
  ],
  "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.

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/platform/customer-requests/{id}

One institute request, with the requesting account’s email.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/customer-requests/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The institute request.
NameTypeConstraints
idrequired
string (uuid)—
instituteNamerequired
string—
slugrequired
string—
countryrequired
string—
sizeBandrequired
enum: "small" | "medium" | "large" | "xlarge"—
phonerequired
string | null—
statusrequired
enum: "pending" | "approved" | "refused"—
refusalReasonrequired
string | null—
tenantIdrequired
string (uuid) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
accountIdrequired
string (uuid)—
requesterEmailrequired
string (email)—
decidedAtrequired
string (date-time) | null—
decidedByrequired
string (uuid) | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "instituteName": "string",
  "slug": "string",
  "country": "string",
  "sizeBand": "small",
  "phone": "+15551234567",
  "status": "pending",
  "refusalReason": "string",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z",
  "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
  "requesterEmail": "ada.lovelace@example.com",
  "decidedAt": "2026-03-02T09:00:00.000Z",
  "decidedBy": "d4d316ba-d4d3-74d3-8d31-d4d316bad4d3"
}
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.

POST /api/v1/platform/customer-requests/{id}/approve

Approve an institute request: creates the tenant (the "Add a customer" path), sets its owner, mails the requester. Owner only; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
planCoderequired
string1–50 chars
trialDays
integer0–365

Example

json
{
  "planCode": "string",
  "trialDays": 0
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/customer-requests/37386ae0-3738-7738-8386-37386ae03738/approve" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "planCode": "string",
    "trialDays": 0
  }'

Responses

200The approved request, now linked to its tenant.
NameTypeConstraints
idrequired
string (uuid)—
instituteNamerequired
string—
slugrequired
string—
countryrequired
string—
sizeBandrequired
enum: "small" | "medium" | "large" | "xlarge"—
phonerequired
string | null—
statusrequired
enum: "pending" | "approved" | "refused"—
refusalReasonrequired
string | null—
tenantIdrequired
string (uuid) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
accountIdrequired
string (uuid)—
requesterEmailrequired
string (email)—
decidedAtrequired
string (date-time) | null—
decidedByrequired
string (uuid) | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "instituteName": "string",
  "slug": "string",
  "country": "string",
  "sizeBand": "small",
  "phone": "+15551234567",
  "status": "pending",
  "refusalReason": "string",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z",
  "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
  "requesterEmail": "ada.lovelace@example.com",
  "decidedAt": "2026-03-02T09:00:00.000Z",
  "decidedBy": "d4d316ba-d4d3-74d3-8d31-d4d316bad4d3"
}
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.

POST /api/v1/platform/customer-requests/{id}/refuse

Refuse an institute request with a reason, mailed to the requester. Owner only; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
reasonrequired
string1–2000 chars

Example

json
{
  "reason": "Requested by guardian."
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/customer-requests/37386ae0-3738-7738-8386-37386ae03738/refuse" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "reason": "Requested by guardian."
  }'

Responses

200The refused request.
NameTypeConstraints
idrequired
string (uuid)—
instituteNamerequired
string—
slugrequired
string—
countryrequired
string—
sizeBandrequired
enum: "small" | "medium" | "large" | "xlarge"—
phonerequired
string | null—
statusrequired
enum: "pending" | "approved" | "refused"—
refusalReasonrequired
string | null—
tenantIdrequired
string (uuid) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
accountIdrequired
string (uuid)—
requesterEmailrequired
string (email)—
decidedAtrequired
string (date-time) | null—
decidedByrequired
string (uuid) | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "instituteName": "string",
  "slug": "string",
  "country": "string",
  "sizeBand": "small",
  "phone": "+15551234567",
  "status": "pending",
  "refusalReason": "string",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z",
  "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
  "requesterEmail": "ada.lovelace@example.com",
  "decidedAt": "2026-03-02T09:00:00.000Z",
  "decidedBy": "d4d316ba-d4d3-74d3-8d31-d4d316bad4d3"
}
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.

GET /api/v1/platform/installs

Fleet-wide list of self-hosted installs: institute, version, last heartbeat, updater result, relay install state (cursor-paginated).

Requires authenticationScopeNo additional permission

Path & query parameters

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

curl example

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

Responses

200A page of installs across every tenant.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "licenseId": "f5f084e9-f5f0-75f0-8f08-f5f084e9f5f0",
      "installId": "2472867d-2472-7472-8728-2472867d2472",
      "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
      "tenantName": "string",
      "tenantSlug": "string",
      "appVersion": "string",
      "lastSeenAt": "2026-03-02T09:00:00.000Z",
      "firstSeenAt": "2026-03-02T09:00:00.000Z",
      "updateChannel": "stable",
      "updaterLastCheckAt": "2026-03-02T09:00:00.000Z",
      "updaterLastResult": "up_to_date",
      "updaterLastReason": "none",
      "backup": {
        "lastStatus": "success",
        "lastRunAt": "2026-03-02T09:00:00.000Z",
        "lastSuccessAt": "2026-03-02T09:00:00.000Z"
      },
      "guardian": {
        "version": 1,
        "updaterVersion": "string",
        "lastTickAt": "2026-03-02T09:00:00.000Z"
      },
      "needsAdoption": true,
      "relayInstallStatus": "approved",
      "address": "string",
      "serverIp": "string",
      "state": "provisioning"
    }
  ],
  "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.

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/platform/installs/{id}/dns

Restore this install's DNS A record (docs/specs/phase12-one-step-install.md §2.5). Refused for a non-active customer, an own-domain install, or one with no stored address.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The install, unchanged, once its DNS record is restored.
NameTypeConstraints
idrequired
string (uuid)—
licenseIdrequired
string (uuid)—
installIdrequired
string (uuid)—
tenantIdrequired
string (uuid)—
tenantNamerequired
string—
tenantSlugrequired
string—
appVersionrequired
string | null—
lastSeenAtrequired
string (date-time) | null—
firstSeenAtrequired
string (date-time)—
updateChannelrequired
enum: "stable" | "beta" | null | null—
updaterLastCheckAtrequired
string (date-time) | null—
updaterLastResultrequired
enum: "up_to_date" | "updated" | "rolled_back" | "failed" | "skipped" | null | null—
updaterLastReasonrequired
enum: "none" | "auto_update_off" | "min_from_version" | "locked" | "migration_running" | "manifest_unreachable" | "bad_signature" | "channel_mismatch" | "digest_mismatch" | "docker_error" | "health_check_failed" | "api_unreachable" | "setup_pending" | null | null—
backuprequired
object | null—
guardianrequired
object | null—
needsAdoptionrequired
boolean—
relayInstallStatusrequired
enum: "approved" | "pending" | "blocked" | null | null—
addressrequired
string | null—
serverIprequired
string | null—
staterequired
enum: "provisioning" | "running" | "unreachable" | null | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "licenseId": "f5f084e9-f5f0-75f0-8f08-f5f084e9f5f0",
  "installId": "2472867d-2472-7472-8728-2472867d2472",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "tenantName": "string",
  "tenantSlug": "string",
  "appVersion": "string",
  "lastSeenAt": "2026-03-02T09:00:00.000Z",
  "firstSeenAt": "2026-03-02T09:00:00.000Z",
  "updateChannel": "stable",
  "updaterLastCheckAt": "2026-03-02T09:00:00.000Z",
  "updaterLastResult": "up_to_date",
  "updaterLastReason": "none",
  "backup": {
    "lastStatus": "success",
    "lastRunAt": "2026-03-02T09:00:00.000Z",
    "lastSuccessAt": "2026-03-02T09:00:00.000Z"
  },
  "guardian": {
    "version": 1,
    "updaterVersion": "string",
    "lastTickAt": "2026-03-02T09:00:00.000Z"
  },
  "needsAdoption": true,
  "relayInstallStatus": "approved",
  "address": "string",
  "serverIp": "string",
  "state": "provisioning"
}
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.

GET /api/v1/platform/licenses/{licenseId}/installs

Installs seen on this license, with last-seen, version, tamper signals and pending commands.

Requires authenticationScopeNo additional permission

Path & query parameters

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

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/licenses/f5f084e9-f5f0-75f0-8f08-f5f084e9f5f0/installs" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200A page of installs.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "licenseId": "f5f084e9-f5f0-75f0-8f08-f5f084e9f5f0",
      "installId": "2472867d-2472-7472-8728-2472867d2472",
      "appVersion": "string",
      "lastSeenAt": "2026-03-02T09:00:00.000Z",
      "heartbeatCounter": 0,
      "signals": [
        {
          "kind": "silent",
          "severity": "info",
          "detectedAt": "2026-03-02T09:00:00.000Z",
          "detail": "string"
        }
      ],
      "firstSeenAt": "2026-03-02T09:00:00.000Z",
      "lastWitnessAt": "2026-03-02T09:00:00.000Z",
      "updateChannel": "stable",
      "updaterLastCheckAt": "2026-03-02T09:00:00.000Z",
      "updaterLastResult": "up_to_date",
      "updaterLastReason": "none",
      "pendingCommands": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "kind": "lock",
          "reason": "Requested by guardian.",
          "issuedAt": "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.

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/platform/licenses/{licenseId}/lock

Lock a self-hosted install: issues a signed lock command applied on its next heartbeat.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
licenseIdrequired
pathstring—

Request body

NameTypeConstraints
reasonrequired
string1–500 chars

Example

json
{
  "reason": "Requested by guardian."
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/licenses/f5f084e9-f5f0-75f0-8f08-f5f084e9f5f0/lock" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "reason": "Requested by guardian."
  }'

Responses

201The signed lock command awaiting acknowledgement.
NameTypeConstraints
idrequired
string (uuid)—
licenseIdrequired
string (uuid)—
kindrequired
enum: "lock" | "unlock"—
reasonrequired
string | null—
issuedAtrequired
string (date-time)—
acknowledgedAtrequired
string (date-time) | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "licenseId": "f5f084e9-f5f0-75f0-8f08-f5f084e9f5f0",
  "kind": "lock",
  "reason": "Requested by guardian.",
  "issuedAt": "2026-03-02T09:00:00.000Z",
  "acknowledgedAt": "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.

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/platform/licenses/{licenseId}/unlock

Unlock a self-hosted install: issues a signed unlock command.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
licenseIdrequired
pathstring—

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/licenses/f5f084e9-f5f0-75f0-8f08-f5f084e9f5f0/unlock" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

201The signed unlock command awaiting acknowledgement.
NameTypeConstraints
idrequired
string (uuid)—
licenseIdrequired
string (uuid)—
kindrequired
enum: "lock" | "unlock"—
reasonrequired
string | null—
issuedAtrequired
string (date-time)—
acknowledgedAtrequired
string (date-time) | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "licenseId": "f5f084e9-f5f0-75f0-8f08-f5f084e9f5f0",
  "kind": "lock",
  "reason": "Requested by guardian.",
  "issuedAt": "2026-03-02T09:00:00.000Z",
  "acknowledgedAt": "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.

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/platform/licenses/{licenseId}/unlock-token

Mint a one-time signed unlock token the customer pastes into their self-hosted admin when the license server is unreachable.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
licenseIdrequired
pathstring—

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/licenses/f5f084e9-f5f0-75f0-8f08-f5f084e9f5f0/unlock-token" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

201The one-time unlock token. Shown once — it is not retrievable again.
NameTypeConstraints
tokenrequired
string—
expiresAtrequired
string (date-time)—
json
{
  "token": "string",
  "expiresAt": "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.

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/platform/licenses/heartbeat

Self-hosted install heartbeat, signed with the relay identity. Returns a signed envelope carrying server time, state, and any pending lock/unlock commands.

No authenticationScopePublic

Request body

NameTypeConstraints
jtirequired
string1–200 chars
appVersionrequired
string1–50 chars
counterrequired
integer0–9007199254740991
clientTimerequired
string (date-time)—
maxObservedTimerequired
string (date-time) | null—
noncerequired
string1–100 chars
appliedCommandIds
array<string (uuid)>default: []
updater
object—
backup
object—
guardian
object—

Example

json
{
  "jti": "string",
  "appVersion": "string",
  "counter": 0,
  "clientTime": "2026-03-02T09:00:00.000Z",
  "maxObservedTime": "2026-03-02T09:00:00.000Z",
  "nonce": "string",
  "appliedCommandIds": [
    "3a42c45f-3a42-7a42-842c-3a42c45f3a42"
  ],
  "updater": {
    "lastCheckAt": "2026-03-02T09:00:00.000Z",
    "lastResult": "up_to_date",
    "reason": "none",
    "channel": "stable"
  },
  "backup": {
    "lastStatus": "success",
    "lastRunAt": "2026-03-02T09:00:00.000Z",
    "lastSuccessAt": "2026-03-02T09:00:00.000Z"
  },
  "guardian": {
    "version": 1,
    "updaterVersion": "string",
    "lastTickAt": "2026-03-02T09:00:00.000Z"
  }
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/licenses/heartbeat" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "jti": "string",
    "appVersion": "string",
    "counter": 0,
    "clientTime": "2026-03-02T09:00:00.000Z",
    "maxObservedTime": "2026-03-02T09:00:00.000Z",
    "nonce": "string",
    "appliedCommandIds": [
      "3a42c45f-3a42-7a42-842c-3a42c45f3a42"
    ],
    "updater": {
      "lastCheckAt": "2026-03-02T09:00:00.000Z",
      "lastResult": "up_to_date",
      "reason": "none",
      "channel": "stable"
    },
    "backup": {
      "lastStatus": "success",
      "lastRunAt": "2026-03-02T09:00:00.000Z",
      "lastSuccessAt": "2026-03-02T09:00:00.000Z"
    },
    "guardian": {
      "version": 1,
      "updaterVersion": "string",
      "lastTickAt": "2026-03-02T09:00:00.000Z"
    }
  }'

Responses

200A signed heartbeat envelope.
NameTypeConstraints
enveloperequired
string—
json
{
  "envelope": "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.

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/platform/licenses/releases/manifest

The signed release manifest for a channel. Verified against the license public key baked into every self-hosted image.

No authenticationScopePublic

Path & query parameters

NameInTypeConstraints
channel
queryenum: "stable" | "beta"default: "stable"

curl example

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

Responses

200The signed manifest (compact JWS) and, advisory only, its decoded payload.
NameTypeConstraints
manifestrequired
string—
payloadrequired
object—
json
{
  "manifest": "string",
  "payload": {
    "channel": "stable",
    "version": "string",
    "images": {
      "api": {
        "ref": "string",
        "digest": "string"
      },
      "web": {
        "ref": "string",
        "digest": "string"
      }
    },
    "minFromVersion": "string",
    "migrations": "forward-only",
    "releasedAt": "2026-03-02T09:00:00.000Z",
    "notes": "Prefers afternoon sessions."
  }
}
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.

GET /api/v1/platform/licenses/releases/v2/manifest

The signed release manifest, version 2 (updater and backup images, desired-state sha256, repair mode), for a channel.

No authenticationScopePublic

Path & query parameters

NameInTypeConstraints
channel
queryenum: "stable" | "beta"default: "stable"

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/licenses/releases/v2/manifest" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The signed v2 manifest (compact JWS) and, advisory only, its decoded payload.
NameTypeConstraints
manifestrequired
string—
payloadrequired
object—
json
{
  "manifest": "string",
  "payload": {
    "schema": 2,
    "channel": "stable",
    "version": "string",
    "images": {
      "api": {
        "ref": "string",
        "digest": "string"
      },
      "web": {
        "ref": "string",
        "digest": "string"
      },
      "backup": {
        "ref": "string",
        "digest": "string"
      },
      "updater": {
        "ref": "string",
        "digest": "string"
      }
    },
    "desiredState": {
      "sha256": "string",
      "updaterProtocol": 1
    },
    "repairMode": "repair",
    "offsite": {
      "uploadHosts": [
        "string"
      ]
    },
    "minFromVersion": "string",
    "migrations": "forward-only",
    "releasedAt": "2026-03-02T09:00:00.000Z",
    "notes": "Prefers afternoon sessions."
  }
}
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.

GET /api/v1/platform/licenses/releases/v2/manifest/{version}

The signed v2 manifest of one published version.

No authenticationScopePublic

Path & query parameters

NameInTypeConstraints
versionrequired
pathstring—

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/licenses/releases/v2/manifest/string" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The signed v2 manifest (compact JWS) of that version and, advisory only, its payload.
NameTypeConstraints
manifestrequired
string—
payloadrequired
object—
json
{
  "manifest": "string",
  "payload": {
    "schema": 2,
    "channel": "stable",
    "version": "string",
    "images": {
      "api": {
        "ref": "string",
        "digest": "string"
      },
      "web": {
        "ref": "string",
        "digest": "string"
      },
      "backup": {
        "ref": "string",
        "digest": "string"
      },
      "updater": {
        "ref": "string",
        "digest": "string"
      }
    },
    "desiredState": {
      "sha256": "string",
      "updaterProtocol": 1
    },
    "repairMode": "repair",
    "offsite": {
      "uploadHosts": [
        "string"
      ]
    },
    "minFromVersion": "string",
    "migrations": "forward-only",
    "releasedAt": "2026-03-02T09:00:00.000Z",
    "notes": "Prefers afternoon sessions."
  }
}
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.

POST /api/v1/platform/login-discovery

Resolve an email to every host it can sign in on (platform admin / Institflow account / institute install) — identical response shape whether the email is known or not.

No authenticationScopePublic

Request body

NameTypeConstraints
emailrequired
string (email)0–320 chars

Example

json
{
  "email": "ada.lovelace@example.com"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/login-discovery" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "email": "ada.lovelace@example.com"
  }'

Responses

200Zero or more login outcomes.
NameTypeConstraints
outcomesrequired
array<unknown>—
json
{
  "outcomes": [
    "value"
  ]
}
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.

GET /api/v1/platform/me

The current platform admin’s profile, 2FA status and session count.

Requires authenticationScopeNo additional permission

curl example

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

Responses

200The profile.
NameTypeConstraints
userIdrequired
string (uuid)—
emailrequired
string (email)—
pendingEmailrequired
string (email) | null—
fullNamerequired
string—
localerequired
enum: "ar" | "en" | "he"—
rolerequired
enum: "owner" | "support"—
totpEnrolledrequired
boolean—
sessionCountrequired
integer-9007199254740991–9007199254740991
json
{
  "userId": "11f967df-11f9-71f9-8f96-11f967df11f9",
  "email": "ada.lovelace@example.com",
  "pendingEmail": "ada.lovelace@example.com",
  "fullName": "Ada Lovelace",
  "locale": "ar",
  "role": "owner",
  "totpEnrolled": true,
  "sessionCount": -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.

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/platform/me/change-email

Request an email change — sends a confirmation link to the NEW address. Requires the current password and a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
newEmailrequired
string (email)—
currentPasswordrequired
string≥1 chars

Example

json
{
  "newEmail": "ada.lovelace@example.com",
  "currentPassword": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/me/change-email" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "newEmail": "ada.lovelace@example.com",
    "currentPassword": "string"
  }'

Responses

200A confirmation link was sent.
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.

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/platform/me/change-password

Change your own password. Revokes every OTHER session (this one stays signed in).

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
currentPasswordrequired
string≥1 chars
newPasswordrequired
string≥10 chars

Example

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

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/me/change-password" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "currentPassword": "string",
    "newPassword": "string"
  }'

Responses

200The password was changed.
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.

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/platform/me/confirm-email

Confirm a pending email change from the link sent to the new address.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
tokenrequired
string≥1 chars

Example

json
{
  "token": "string"
}

curl example

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

Responses

200The email address was changed.
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.

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/platform/me/sessions

Every active session for the current platform admin.

Requires authenticationScopeNo additional permission

curl example

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

Responses

200The active sessions.
json
[
  {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "userAgent": "string",
    "ip": "string",
    "lastUsedAt": "2026-03-02T09:00:00.000Z",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "mfaAt": "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.

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/platform/me/sessions/{id}

Revoke one of your own sessions.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The session was revoked.
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.

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/platform/me/totp/enable

Complete TOTP enrolment with a live code from the app. This *is* the login the mfa/enroll challenge stood in for: returns the real token pair, plus 10 recovery codes shown exactly once.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
coderequired
string—
client
enum: "web" | "mobile"default: "web"

Example

json
{
  "code": "string",
  "client": "web"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/me/totp/enable" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "code": "string",
    "client": "web"
  }'

Responses

201Tokens and the recovery codes.
NameTypeConstraints
accessTokenrequired
string—
refreshToken
string—
userrequired
object—
membershipsrequired
array<MembershipSummary>—
activeTenantIdrequired
string (uuid) | null—
recoveryCodesrequired
array<string>—
json
{
  "accessToken": "string",
  "refreshToken": "string",
  "user": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "email": "ada.lovelace@example.com",
    "fullName": "Ada Lovelace",
    "locale": "ar",
    "status": "active",
    "emailVerified": true
  },
  "memberships": [
    {
      "membershipId": "e34c26d7-e34c-734c-84c2-e34c26d7e34c",
      "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
      "tenantSlug": "string",
      "tenantName": "string",
      "status": "invited",
      "roles": [
        {
          "roleId": "5b007a17-5b00-7b00-8007-5b007a175b00",
          "roleName": "string",
          "roleKey": "string"
        }
      ]
    }
  ],
  "activeTenantId": "f056e4d2-f056-7056-856e-f056e4d2f056",
  "recoveryCodes": [
    "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.

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/platform/me/totp/regenerate-recovery-codes

Replace every recovery code. Requires a live TOTP code (re-authenticates in place of a step-up).

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
coderequired
string—

Example

json
{
  "code": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/me/totp/regenerate-recovery-codes" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "code": "string"
  }'

Responses

200The new recovery codes.
NameTypeConstraints
recoveryCodesrequired
array<string>—
json
{
  "recoveryCodes": [
    "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.

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/platform/me/totp/setup

Begin TOTP enrolment: returns the otpauth:// URI + secret (shown once). Refused once 2FA is already enabled.

Requires authenticationScopeNo additional permission

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/me/totp/setup" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The pending secret.
NameTypeConstraints
otpauthUrirequired
string—
secretBase32required
string—
json
{
  "otpauthUri": "string",
  "secretBase32": "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.

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/platform/plans

List every plan.

Requires authenticationScopeNo additional permission

curl example

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

Responses

200Every plan.
json
[
  {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "code": "string",
    "name": "Algebra I",
    "priceMinor": 12000,
    "currency": "USD",
    "billingInterval": "month",
    "limits": {
      "maxStudents": -9007199254740991,
      "maxBranches": -9007199254740991,
      "maxVideoCourses": -9007199254740991,
      "maxLessonsPerCourse": -9007199254740991,
      "notificationQuota": {
        "whatsapp": -9007199254740991,
        "sms": -9007199254740991,
        "email": "ada.lovelace@example.com"
      }
    },
    "features": [
      "string"
    ],
    "isActive": 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.

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/platform/plans

Create a plan.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
coderequired
string1–50 chars
namerequired
string1–200 chars
priceMinorrequired
integer0–9007199254740991
currencyrequired
string3–3 chars
billingIntervalrequired
enum: "month" | "year"—
limitsrequired
object—
featuresrequired
array<string>—

Example

json
{
  "code": "string",
  "name": "Algebra I",
  "priceMinor": 12000,
  "currency": "USD",
  "billingInterval": "month",
  "limits": {
    "maxStudents": 0,
    "maxBranches": 0,
    "maxVideoCourses": -1,
    "maxLessonsPerCourse": -1,
    "notificationQuota": {
      "whatsapp": 0,
      "sms": 0,
      "email": "ada.lovelace@example.com"
    }
  },
  "features": [
    "string"
  ]
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/plans" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "code": "string",
    "name": "Algebra I",
    "priceMinor": 12000,
    "currency": "USD",
    "billingInterval": "month",
    "limits": {
      "maxStudents": 0,
      "maxBranches": 0,
      "maxVideoCourses": -1,
      "maxLessonsPerCourse": -1,
      "notificationQuota": {
        "whatsapp": 0,
        "sms": 0,
        "email": "ada.lovelace@example.com"
      }
    },
    "features": [
      "string"
    ]
  }'

Responses

201The newly created plan.
NameTypeConstraints
idrequired
string (uuid)—
coderequired
string—
namerequired
string—
priceMinorrequired
integer-9007199254740991–9007199254740991
currencyrequired
string3–3 chars
billingIntervalrequired
enum: "month" | "year"—
limitsrequired
PlanLimits—
featuresrequired
array<string>—
isActiverequired
boolean—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "code": "string",
  "name": "Algebra I",
  "priceMinor": 12000,
  "currency": "USD",
  "billingInterval": "month",
  "limits": {
    "maxStudents": -9007199254740991,
    "maxBranches": -9007199254740991,
    "maxVideoCourses": -9007199254740991,
    "maxLessonsPerCourse": -9007199254740991,
    "notificationQuota": {
      "whatsapp": -9007199254740991,
      "sms": -9007199254740991,
      "email": "ada.lovelace@example.com"
    }
  },
  "features": [
    "string"
  ],
  "isActive": 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.

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.

PATCH /api/v1/platform/plans/{id}

Update a plan (use `isActive: false` to deactivate — plans are never deleted).

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
name
string1–200 chars
priceMinor
integer0–9007199254740991
currency
string3–3 chars
billingInterval
enum: "month" | "year"—
limits
object—
features
array<string>—
isActive
boolean—

Example

json
{
  "name": "Algebra I",
  "priceMinor": 12000,
  "currency": "USD",
  "billingInterval": "month",
  "limits": {
    "maxStudents": 0,
    "maxBranches": 0,
    "maxVideoCourses": -1,
    "maxLessonsPerCourse": -1,
    "notificationQuota": {
      "whatsapp": 0,
      "sms": 0,
      "email": "ada.lovelace@example.com"
    }
  },
  "features": [
    "string"
  ],
  "isActive": true
}

curl example

curl
curl -X PATCH "https://your-institute.example/api/v1/platform/plans/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Algebra I",
    "priceMinor": 12000,
    "currency": "USD",
    "billingInterval": "month",
    "limits": {
      "maxStudents": 0,
      "maxBranches": 0,
      "maxVideoCourses": -1,
      "maxLessonsPerCourse": -1,
      "notificationQuota": {
        "whatsapp": 0,
        "sms": 0,
        "email": "ada.lovelace@example.com"
      }
    },
    "features": [
      "string"
    ],
    "isActive": true
  }'

Responses

200The updated plan.
NameTypeConstraints
idrequired
string (uuid)—
coderequired
string—
namerequired
string—
priceMinorrequired
integer-9007199254740991–9007199254740991
currencyrequired
string3–3 chars
billingIntervalrequired
enum: "month" | "year"—
limitsrequired
PlanLimits—
featuresrequired
array<string>—
isActiverequired
boolean—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "code": "string",
  "name": "Algebra I",
  "priceMinor": 12000,
  "currency": "USD",
  "billingInterval": "month",
  "limits": {
    "maxStudents": -9007199254740991,
    "maxBranches": -9007199254740991,
    "maxVideoCourses": -9007199254740991,
    "maxLessonsPerCourse": -9007199254740991,
    "notificationQuota": {
      "whatsapp": -9007199254740991,
      "sms": -9007199254740991,
      "email": "ada.lovelace@example.com"
    }
  },
  "features": [
    "string"
  ],
  "isActive": 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.

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/platform/queues

Job counts (waiting/active/failed/delayed) per BullMQ queue (docs/ARCHITECTURE.md §4.7).

Requires authenticationScopeNo additional permission

curl example

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

Responses

200Counts per queue.
NameTypeConstraints
maintenancerequired
QueueJobCounts—
notificationsrequired
QueueJobCounts—
webhooksrequired
QueueJobCounts—
reportsrequired
QueueJobCounts—
digestrequired
QueueJobCounts—
billingrequired
QueueJobCounts—
academicsrequired
QueueJobCounts—
remindersrequired
QueueJobCounts—
relay-system-mailrequired
QueueJobCounts—
account-links-syncrequired
QueueJobCounts—
support-submissionsrequired
QueueJobCounts—
json
{
  "maintenance": {
    "waiting": -9007199254740991,
    "active": -9007199254740991,
    "failed": -9007199254740991,
    "delayed": -9007199254740991
  },
  "notifications": {
    "waiting": -9007199254740991,
    "active": -9007199254740991,
    "failed": -9007199254740991,
    "delayed": -9007199254740991
  },
  "webhooks": {
    "waiting": -9007199254740991,
    "active": -9007199254740991,
    "failed": -9007199254740991,
    "delayed": -9007199254740991
  },
  "reports": {
    "waiting": -9007199254740991,
    "active": -9007199254740991,
    "failed": -9007199254740991,
    "delayed": -9007199254740991
  },
  "digest": {
    "waiting": -9007199254740991,
    "active": -9007199254740991,
    "failed": -9007199254740991,
    "delayed": -9007199254740991
  },
  "billing": {
    "waiting": -9007199254740991,
    "active": -9007199254740991,
    "failed": -9007199254740991,
    "delayed": -9007199254740991
  },
  "academics": {
    "waiting": -9007199254740991,
    "active": -9007199254740991,
    "failed": -9007199254740991,
    "delayed": -9007199254740991
  },
  "reminders": {
    "waiting": -9007199254740991,
    "active": -9007199254740991,
    "failed": -9007199254740991,
    "delayed": -9007199254740991
  },
  "relay-system-mail": {
    "waiting": -9007199254740991,
    "active": -9007199254740991,
    "failed": -9007199254740991,
    "delayed": -9007199254740991
  },
  "account-links-sync": {
    "waiting": -9007199254740991,
    "active": -9007199254740991,
    "failed": -9007199254740991,
    "delayed": -9007199254740991
  },
  "support-submissions": {
    "waiting": -9007199254740991,
    "active": -9007199254740991,
    "failed": -9007199254740991,
    "delayed": -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.

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/platform/registry/token

Docker Registry v2 token endpoint. HTTP Basic credentials (a registry_credentials row, or the vendor release identity); scope/service per the Docker Registry v2 token spec.

No authenticationScopePublic

curl example

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

Responses

200A signed bearer token.
NameTypeConstraints
tokenrequired
string—
access_tokenrequired
string—
json
{
  "token": "string",
  "access_token": "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.

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/platform/relay/account

This install's account snapshot: balance, overdraft, prices in force, install status.

No authenticationScopePublic

curl example

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

Responses

200The account snapshot.
NameTypeConstraints
balanceUnitsrequired
integer-9007199254740991–9007199254740991
overdraftUnitsrequired
integer-9007199254740991–9007199254740991
statusrequired
enum: "active" | "closed"—
reputationHoldrequired
boolean—
relayEmailEnabledrequired
boolean—
installStatusrequired
enum: "approved" | "pending" | "blocked"—
pricesrequired
array<object>—
senderNamerequired
object—
json
{
  "balanceUnits": -9007199254740991,
  "overdraftUnits": -9007199254740991,
  "status": "active",
  "reputationHold": true,
  "relayEmailEnabled": true,
  "installStatus": "approved",
  "prices": [
    {
      "senderMode": "platform",
      "priceUnits": -9007199254740991
    }
  ],
  "senderName": {
    "displayName": "string",
    "state": "none"
  }
}
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.

POST /api/v1/platform/relay/account/sender-name

Submit this install’s requested institute-mode display name for the owner’s one-time impersonation-check approval (§11.3).

No authenticationScopePublic

Request body

NameTypeConstraints
displayNamerequired
string1–70 chars

Example

json
{
  "displayName": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/relay/account/sender-name" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "displayName": "string"
  }'

Responses

200Confirmation — the request now shows as pending.
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.

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/platform/relay/accounts

List relay accounts: balance, overdraft, 30-day spend, and the owning tenant’s name/slug.

Requires authenticationScopeNo additional permission

Path & query parameters

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

curl example

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

Responses

200A page of relay accounts.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
      "balanceUnits": -9007199254740991,
      "overdraftUnits": -9007199254740991,
      "status": "active",
      "ratePerMinute": -9007199254740991,
      "ratePerDay": -9007199254740991,
      "reputationHold": true,
      "reputationHoldReason": "string",
      "senderDisplayName": "string",
      "senderNameApprovedAt": "2026-03-02T09:00:00.000Z",
      "senderNameRequestedAt": "2026-03-02T09:00:00.000Z",
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z",
      "spend30dUnits": -9007199254740991,
      "installCount": -9007199254740991,
      "pendingInstallCount": -9007199254740991,
      "tenantSlug": "string",
      "tenantName": "string"
    }
  ],
  "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.

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/platform/relay/accounts/{tenantId}

A single tenant’s relay account, with its 30-day spend.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/relay/accounts/4332af8b-4332-7332-832a-4332af8b4332" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The relay account.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
balanceUnitsrequired
integer-9007199254740991–9007199254740991
overdraftUnitsrequired
integer-9007199254740991–9007199254740991
statusrequired
enum: "active" | "closed"—
ratePerMinuterequired
integer | null-9007199254740991–9007199254740991
ratePerDayrequired
integer | null-9007199254740991–9007199254740991
reputationHoldrequired
boolean—
reputationHoldReasonrequired
string | null—
senderDisplayNamerequired
string | null—
senderNameApprovedAtrequired
string (date-time) | null—
senderNameRequestedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
spend30dUnitsrequired
integer-9007199254740991–9007199254740991
installCountrequired
integer-9007199254740991–9007199254740991
pendingInstallCountrequired
integer-9007199254740991–9007199254740991
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "balanceUnits": -9007199254740991,
  "overdraftUnits": -9007199254740991,
  "status": "active",
  "ratePerMinute": -9007199254740991,
  "ratePerDay": -9007199254740991,
  "reputationHold": true,
  "reputationHoldReason": "string",
  "senderDisplayName": "string",
  "senderNameApprovedAt": "2026-03-02T09:00:00.000Z",
  "senderNameRequestedAt": "2026-03-02T09:00:00.000Z",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z",
  "spend30dUnits": -9007199254740991,
  "installCount": -9007199254740991,
  "pendingInstallCount": -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.

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.

PATCH /api/v1/platform/relay/accounts/{tenantId}

Change a relay account’s overdraft limit, per-channel rate limits or reputation hold. Owner only; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

Request body

NameTypeConstraints
overdraftUnits
integer0–9007199254740991
ratePerMinute
integer | null≤9007199254740991
ratePerDay
integer | null≤9007199254740991
reputationHold
boolean—
reputationHoldReason
string | null1–500 chars
senderNameApproved
boolean—

Example

json
{
  "overdraftUnits": 0,
  "ratePerMinute": 1,
  "ratePerDay": 1,
  "reputationHold": true,
  "reputationHoldReason": "string",
  "senderNameApproved": true
}

curl example

curl
curl -X PATCH "https://your-institute.example/api/v1/platform/relay/accounts/4332af8b-4332-7332-832a-4332af8b4332" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "overdraftUnits": 0,
    "ratePerMinute": 1,
    "ratePerDay": 1,
    "reputationHold": true,
    "reputationHoldReason": "string",
    "senderNameApproved": true
  }'

Responses

200The updated relay account.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
balanceUnitsrequired
integer-9007199254740991–9007199254740991
overdraftUnitsrequired
integer-9007199254740991–9007199254740991
statusrequired
enum: "active" | "closed"—
ratePerMinuterequired
integer | null-9007199254740991–9007199254740991
ratePerDayrequired
integer | null-9007199254740991–9007199254740991
reputationHoldrequired
boolean—
reputationHoldReasonrequired
string | null—
senderDisplayNamerequired
string | null—
senderNameApprovedAtrequired
string (date-time) | null—
senderNameRequestedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "balanceUnits": -9007199254740991,
  "overdraftUnits": -9007199254740991,
  "status": "active",
  "ratePerMinute": -9007199254740991,
  "ratePerDay": -9007199254740991,
  "reputationHold": true,
  "reputationHoldReason": "string",
  "senderDisplayName": "string",
  "senderNameApprovedAt": "2026-03-02T09:00:00.000Z",
  "senderNameRequestedAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/relay/accounts/{tenantId}/adjustments

Record a manual balance correction (either sign, reason mandatory). Owner only; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

Request body

NameTypeConstraints
amountUnitsrequired
integer-9007199254740991–9007199254740991
reasonrequired
string1–500 chars
note
string0–2000 chars

Example

json
{
  "amountUnits": -9007199254740991,
  "reason": "Requested by guardian.",
  "note": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/relay/accounts/4332af8b-4332-7332-832a-4332af8b4332/adjustments" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "amountUnits": -9007199254740991,
    "reason": "Requested by guardian.",
    "note": "string"
  }'

Responses

201The adjustment ledger entry.
NameTypeConstraints
idrequired
string (uuid)—
accountIdrequired
string (uuid)—
kindrequired
enum: "topup" | "debit" | "refund" | "adjustment"—
amountUnitsrequired
integer-9007199254740991–9007199254740991
balanceAfterUnitsrequired
integer-9007199254740991–9007199254740991
messageIdrequired
string (uuid) | null—
priceIdrequired
string (uuid) | null—
reasonrequired
string | null—
noterequired
string | null—
actorUserIdrequired
string (uuid) | null—
createdAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
  "kind": "topup",
  "amountUnits": -9007199254740991,
  "balanceAfterUnits": -9007199254740991,
  "messageId": "1ac1c849-1ac1-7ac1-8c1c-1ac1c8491ac1",
  "priceId": "48d77787-48d7-78d7-8d77-48d7778748d7",
  "reason": "Requested by guardian.",
  "note": "string",
  "actorUserId": "7d892c08-7d89-7d89-8892-7d892c087d89",
  "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.

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/platform/relay/accounts/{tenantId}/ledger

The tenant’s relay ledger, newest first.

Requires authenticationScopeNo additional permission

Path & query parameters

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

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/relay/accounts/4332af8b-4332-7332-832a-4332af8b4332/ledger" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200A page of ledger entries.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
      "kind": "topup",
      "amountUnits": -9007199254740991,
      "balanceAfterUnits": -9007199254740991,
      "messageId": "1ac1c849-1ac1-7ac1-8c1c-1ac1c8491ac1",
      "priceId": "48d77787-48d7-78d7-8d77-48d7778748d7",
      "reason": "Requested by guardian.",
      "note": "string",
      "actorUserId": "7d892c08-7d89-7d89-8892-7d892c087d89",
      "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.

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/platform/relay/accounts/{tenantId}/sender-name/approve

Approve this account’s pending sender-name request, after the owner’s own impersonation check. The body must carry the exact displayName the console rendered — 409 CONFLICT if the install changed it since (TOCTOU guard, H3). Owner only, audited. Requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

Request body

NameTypeConstraints
displayNamerequired
string1–70 chars

Example

json
{
  "displayName": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/relay/accounts/4332af8b-4332-7332-832a-4332af8b4332/sender-name/approve" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "displayName": "string"
  }'

Responses

200The updated relay account.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
balanceUnitsrequired
integer-9007199254740991–9007199254740991
overdraftUnitsrequired
integer-9007199254740991–9007199254740991
statusrequired
enum: "active" | "closed"—
ratePerMinuterequired
integer | null-9007199254740991–9007199254740991
ratePerDayrequired
integer | null-9007199254740991–9007199254740991
reputationHoldrequired
boolean—
reputationHoldReasonrequired
string | null—
senderDisplayNamerequired
string | null—
senderNameApprovedAtrequired
string (date-time) | null—
senderNameRequestedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "balanceUnits": -9007199254740991,
  "overdraftUnits": -9007199254740991,
  "status": "active",
  "ratePerMinute": -9007199254740991,
  "ratePerDay": -9007199254740991,
  "reputationHold": true,
  "reputationHoldReason": "string",
  "senderDisplayName": "string",
  "senderNameApprovedAt": "2026-03-02T09:00:00.000Z",
  "senderNameRequestedAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/relay/accounts/{tenantId}/sender-name/reject

Reject this account’s pending sender-name request — clears the requested name so the institute goes back to "none". The body must carry the exact displayName the console rendered — 409 CONFLICT if the install changed it since (TOCTOU guard, H3). Owner only, audited. Requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

Request body

NameTypeConstraints
displayNamerequired
string1–70 chars

Example

json
{
  "displayName": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/relay/accounts/4332af8b-4332-7332-832a-4332af8b4332/sender-name/reject" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "displayName": "string"
  }'

Responses

200The updated relay account.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
balanceUnitsrequired
integer-9007199254740991–9007199254740991
overdraftUnitsrequired
integer-9007199254740991–9007199254740991
statusrequired
enum: "active" | "closed"—
ratePerMinuterequired
integer | null-9007199254740991–9007199254740991
ratePerDayrequired
integer | null-9007199254740991–9007199254740991
reputationHoldrequired
boolean—
reputationHoldReasonrequired
string | null—
senderDisplayNamerequired
string | null—
senderNameApprovedAtrequired
string (date-time) | null—
senderNameRequestedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "balanceUnits": -9007199254740991,
  "overdraftUnits": -9007199254740991,
  "status": "active",
  "ratePerMinute": -9007199254740991,
  "ratePerDay": -9007199254740991,
  "reputationHold": true,
  "reputationHoldReason": "string",
  "senderDisplayName": "string",
  "senderNameApprovedAt": "2026-03-02T09:00:00.000Z",
  "senderNameRequestedAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/relay/accounts/{tenantId}/suppressions

The tenant’s suppressed recipients (hashed, never readable), newest first.

Requires authenticationScopeNo additional permission

Path & query parameters

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

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/relay/accounts/4332af8b-4332-7332-832a-4332af8b4332/suppressions" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200A page of suppressions.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "recipientHash": "string",
      "reason": "Requested by guardian.",
      "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.

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/platform/relay/accounts/{tenantId}/suppressions/{id}

Lift a suppression so the relay may send to this recipient again. Owner only, audited. Requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—
idrequired
pathstring—

curl example

curl
curl -X DELETE "https://your-institute.example/api/v1/platform/relay/accounts/4332af8b-4332-7332-832a-4332af8b4332/suppressions/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200Confirmation.
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.

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/platform/relay/accounts/{tenantId}/top-ups

Record a hand-received top-up (e.g. cash) to a tenant’s relay balance. Owner only; not idempotent by design — a repeated call records a second row (§11.4). Requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

Request body

NameTypeConstraints
amountUnitsrequired
integer0–9007199254740991
reasonrequired
string1–500 chars
note
string0–2000 chars

Example

json
{
  "amountUnits": 0,
  "reason": "Requested by guardian.",
  "note": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/relay/accounts/4332af8b-4332-7332-832a-4332af8b4332/top-ups" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "amountUnits": 0,
    "reason": "Requested by guardian.",
    "note": "string"
  }'

Responses

201The top-up ledger entry.
NameTypeConstraints
idrequired
string (uuid)—
accountIdrequired
string (uuid)—
kindrequired
enum: "topup" | "debit" | "refund" | "adjustment"—
amountUnitsrequired
integer-9007199254740991–9007199254740991
balanceAfterUnitsrequired
integer-9007199254740991–9007199254740991
messageIdrequired
string (uuid) | null—
priceIdrequired
string (uuid) | null—
reasonrequired
string | null—
noterequired
string | null—
actorUserIdrequired
string (uuid) | null—
createdAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
  "kind": "topup",
  "amountUnits": -9007199254740991,
  "balanceAfterUnits": -9007199254740991,
  "messageId": "1ac1c849-1ac1-7ac1-8c1c-1ac1c8491ac1",
  "priceId": "48d77787-48d7-78d7-8d77-48d7778748d7",
  "reason": "Requested by guardian.",
  "note": "string",
  "actorUserId": "7d892c08-7d89-7d89-8892-7d892c087d89",
  "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.

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/platform/relay/installs

List relay installs, newest first — every account, or one account with ?accountId= (K-41).

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25
accountId
querystring (uuid)—

curl example

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

Responses

200A page of installs.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
      "installId": "2472867d-2472-7472-8728-2472867d2472",
      "status": "approved",
      "label": "string",
      "baseUrl": "string",
      "lastSeenAt": "2026-03-02T09:00:00.000Z",
      "keyReplacedAt": "2026-03-02T09:00:00.000Z",
      "updaterKeyBoundAt": "2026-03-02T09:00:00.000Z",
      "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.

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/platform/relay/installs

Enrol this install with a licence-proven identity. The first install of an account is approved automatically; every further one is pending until the owner approves it.

No authenticationScopePublic

Request body

NameTypeConstraints
licenseKeyrequired
string≥1 chars
installIdrequired
string1–200 chars
publicKeyrequired
string1–4000 chars
baseUrlrequired
string—
label
string | null0–200 chars

Example

json
{
  "licenseKey": "string",
  "installId": "2472867d-2472-7472-8728-2472867d2472",
  "publicKey": "string",
  "baseUrl": "string",
  "label": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/relay/installs" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "licenseKey": "string",
    "installId": "2472867d-2472-7472-8728-2472867d2472",
    "publicKey": "string",
    "baseUrl": "string",
    "label": "string"
  }'

Responses

200The enrolled (or already-enrolled) install.
NameTypeConstraints
idrequired
string (uuid)—
accountIdrequired
string (uuid)—
installIdrequired
string—
statusrequired
enum: "approved" | "pending" | "blocked"—
labelrequired
string | null—
baseUrlrequired
string—
lastSeenAtrequired
string (date-time) | null—
keyReplacedAtrequired
string (date-time) | null—
updaterKeyBoundAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
  "installId": "2472867d-2472-7472-8728-2472867d2472",
  "status": "approved",
  "label": "string",
  "baseUrl": "string",
  "lastSeenAt": "2026-03-02T09:00:00.000Z",
  "keyReplacedAt": "2026-03-02T09:00:00.000Z",
  "updaterKeyBoundAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/relay/installs/{id}/approve

Approve a pending install so it may start spending. Owner only, audited.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The approved install.
NameTypeConstraints
idrequired
string (uuid)—
accountIdrequired
string (uuid)—
installIdrequired
string—
statusrequired
enum: "approved" | "pending" | "blocked"—
labelrequired
string | null—
baseUrlrequired
string—
lastSeenAtrequired
string (date-time) | null—
keyReplacedAtrequired
string (date-time) | null—
updaterKeyBoundAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
  "installId": "2472867d-2472-7472-8728-2472867d2472",
  "status": "approved",
  "label": "string",
  "baseUrl": "string",
  "lastSeenAt": "2026-03-02T09:00:00.000Z",
  "keyReplacedAt": "2026-03-02T09:00:00.000Z",
  "updaterKeyBoundAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/relay/installs/{id}/block

Block an install — approved or pending — so it can never spend again. Owner only, audited.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The blocked install.
NameTypeConstraints
idrequired
string (uuid)—
accountIdrequired
string (uuid)—
installIdrequired
string—
statusrequired
enum: "approved" | "pending" | "blocked"—
labelrequired
string | null—
baseUrlrequired
string—
lastSeenAtrequired
string (date-time) | null—
keyReplacedAtrequired
string (date-time) | null—
updaterKeyBoundAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
  "installId": "2472867d-2472-7472-8728-2472867d2472",
  "status": "approved",
  "label": "string",
  "baseUrl": "string",
  "lastSeenAt": "2026-03-02T09:00:00.000Z",
  "keyReplacedAt": "2026-03-02T09:00:00.000Z",
  "updaterKeyBoundAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/relay/installs/updater-key

Bind (or re-bind) the key this install’s updater generated, so the updater can sign fault reports while the API is down. Signed with the install’s own relay identity — never with the updater key itself.

No authenticationScopePublic

Request body

NameTypeConstraints
publicKeyrequired
string1–4000 chars

Example

json
{
  "publicKey": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/relay/installs/updater-key" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "publicKey": "string"
  }'

Responses

200The install, with the time its updater key was bound.
NameTypeConstraints
idrequired
string (uuid)—
accountIdrequired
string (uuid)—
installIdrequired
string—
statusrequired
enum: "approved" | "pending" | "blocked"—
labelrequired
string | null—
baseUrlrequired
string—
lastSeenAtrequired
string (date-time) | null—
keyReplacedAtrequired
string (date-time) | null—
updaterKeyBoundAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
  "installId": "2472867d-2472-7472-8728-2472867d2472",
  "status": "approved",
  "label": "string",
  "baseUrl": "string",
  "lastSeenAt": "2026-03-02T09:00:00.000Z",
  "keyReplacedAt": "2026-03-02T09:00:00.000Z",
  "updaterKeyBoundAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/relay/messages

This install's account's relay messages, newest first — or, with ?since=, only those updated since then (K-40, for status-mirroring polls).

No authenticationScopePublic

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25
since
querystring (date-time)—

curl example

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

Responses

200A page of relay messages.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "messageClass": "system",
      "templateId": "8146aba6-8146-7146-846a-8146aba68146",
      "channel": "email",
      "senderMode": "platform",
      "status": "accepted",
      "costUnits": -9007199254740991,
      "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.

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/platform/relay/messages

Send a relay email — system (free, vendor-template only) or institute (paid, priced and debited). Idempotent on idempotencyKey.

No authenticationScopePublic

Request body

None.

Example

json
"value"

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/relay/messages" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '"value"'

Responses

200The relay message record.
NameTypeConstraints
idrequired
string (uuid)—
messageClassrequired
enum: "system" | "institute"—
templateIdrequired
string | null—
channelrequired
enum: "email" | "sms" | "push"—
senderModerequired
enum: "platform" | "institute"—
statusrequired
enum: "accepted" | "sending" | "sent" | "delivered" | "bounced" | "complained" | "failed" | "suppressed"—
costUnitsrequired
integer-9007199254740991–9007199254740991
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "messageClass": "system",
  "templateId": "8146aba6-8146-7146-846a-8146aba68146",
  "channel": "email",
  "senderMode": "platform",
  "status": "accepted",
  "costUnits": -9007199254740991,
  "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.

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/platform/relay/messages/{id}

A single relay message this install's account owns.

No authenticationScopePublic

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/relay/messages/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The relay message record.
NameTypeConstraints
idrequired
string (uuid)—
messageClassrequired
enum: "system" | "institute"—
templateIdrequired
string | null—
channelrequired
enum: "email" | "sms" | "push"—
senderModerequired
enum: "platform" | "institute"—
statusrequired
enum: "accepted" | "sending" | "sent" | "delivered" | "bounced" | "complained" | "failed" | "suppressed"—
costUnitsrequired
integer-9007199254740991–9007199254740991
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "messageClass": "system",
  "templateId": "8146aba6-8146-7146-846a-8146aba68146",
  "channel": "email",
  "senderMode": "platform",
  "status": "accepted",
  "costUnits": -9007199254740991,
  "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.

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/platform/relay/prices

The relay price list, newest (effective-from) first.

Requires authenticationScopeNo additional permission

Path & query parameters

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

curl example

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

Responses

200A page of relay prices.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "channel": "email",
      "country": "string",
      "senderMode": "platform",
      "unit": "email_message",
      "priceUnits": -9007199254740991,
      "providerCostUnits": -9007199254740991,
      "effectiveFrom": "2026-03-02T09:00:00.000Z",
      "createdByUserId": "d630cadc-d630-7630-830c-d630cadcd630",
      "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.

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/platform/relay/prices

Add a new price row, effective immediately or from a future instant. Owner only — prices are never updated or deleted, so a change is always a new row.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
channelrequired
enum: "email" | "sms" | "push"—
countryrequired
string2–2 chars
senderModerequired
enum: "platform" | "institute"—
unitrequired
enum: "email_message" | "sms_segment" | "push_message"—
priceUnitsrequired
integer0–9007199254740991
providerCostUnitsrequired
integer0–9007199254740991
effectiveFrom
string (date-time)—

Example

json
{
  "channel": "email",
  "country": "string",
  "senderMode": "platform",
  "unit": "email_message",
  "priceUnits": 0,
  "providerCostUnits": 0,
  "effectiveFrom": "2026-03-02T09:00:00.000Z"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/relay/prices" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "channel": "email",
    "country": "string",
    "senderMode": "platform",
    "unit": "email_message",
    "priceUnits": 0,
    "providerCostUnits": 0,
    "effectiveFrom": "2026-03-02T09:00:00.000Z"
  }'

Responses

201The newly added price row.
NameTypeConstraints
idrequired
string (uuid)—
channelrequired
enum: "email" | "sms" | "push"—
countryrequired
string2–2 chars
senderModerequired
enum: "platform" | "institute"—
unitrequired
enum: "email_message" | "sms_segment" | "push_message"—
priceUnitsrequired
integer-9007199254740991–9007199254740991
providerCostUnitsrequired
integer-9007199254740991–9007199254740991
effectiveFromrequired
string (date-time)—
createdByUserIdrequired
string (uuid)—
createdAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "channel": "email",
  "country": "string",
  "senderMode": "platform",
  "unit": "email_message",
  "priceUnits": -9007199254740991,
  "providerCostUnits": -9007199254740991,
  "effectiveFrom": "2026-03-02T09:00:00.000Z",
  "createdByUserId": "d630cadc-d630-7630-830c-d630cadcd630",
  "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.

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/platform/relay/webhooks/resend

Resend's bounce/complaint/delivered webhook. Svix-signature verified, idempotent.

No authenticationScopePublic

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/relay/webhooks/resend" \
  -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.

GET /api/v1/platform/releases

Published releases, newest first.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25
channel
queryenum: "stable" | "beta"—

curl example

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

Responses

200A page of releases.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "channel": "stable",
      "version": "string",
      "images": {
        "api": {
          "ref": "string",
          "digest": "string"
        },
        "web": {
          "ref": "string",
          "digest": "string"
        },
        "backup": {
          "ref": "string",
          "digest": "string"
        },
        "updater": {
          "ref": "string",
          "digest": "string"
        }
      },
      "desiredStateSha256": "string",
      "repairMode": "repair",
      "minFromVersion": "string",
      "notes": "Prefers afternoon sessions.",
      "releasedAt": "2026-03-02T09:00:00.000Z",
      "promotedAt": "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.

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/platform/releases

Publish a built release (images pinned by digest). Defaults to the beta channel — promotion to stable is a separate, manual step.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
channel
enum: "stable" | "beta"default: "beta"
versionrequired
string—
imagesrequired
object—
desiredState
object—
minFromVersion
string—
notes
string | null1–5000 chars · default: null
releasedAt
string (date-time)—

Example

json
{
  "channel": "beta",
  "version": "string",
  "images": {
    "api": {
      "ref": "string",
      "digest": "string"
    },
    "web": {
      "ref": "string",
      "digest": "string"
    },
    "backup": {
      "ref": "string",
      "digest": "string"
    },
    "updater": {
      "ref": "string",
      "digest": "string"
    }
  },
  "desiredState": {
    "sha256": "string",
    "updaterProtocol": 1
  },
  "minFromVersion": "string",
  "notes": null,
  "releasedAt": "2026-03-02T09:00:00.000Z"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/releases" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "channel": "beta",
    "version": "string",
    "images": {
      "api": {
        "ref": "string",
        "digest": "string"
      },
      "web": {
        "ref": "string",
        "digest": "string"
      },
      "backup": {
        "ref": "string",
        "digest": "string"
      },
      "updater": {
        "ref": "string",
        "digest": "string"
      }
    },
    "desiredState": {
      "sha256": "string",
      "updaterProtocol": 1
    },
    "minFromVersion": "string",
    "notes": null,
    "releasedAt": "2026-03-02T09:00:00.000Z"
  }'

Responses

201The published release.
NameTypeConstraints
idrequired
string (uuid)—
channelrequired
enum: "stable" | "beta"—
versionrequired
string—
imagesrequired
object—
desiredStateSha256required
string | null—
repairModerequired
enum: "repair" | "report"—
minFromVersionrequired
string—
notesrequired
string | null—
releasedAtrequired
string (date-time)—
promotedAtrequired
string (date-time) | null—
warningsrequired
array<enum: "v1_only_newer_than_v2">—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "channel": "stable",
  "version": "string",
  "images": {
    "api": {
      "ref": "string",
      "digest": "string"
    },
    "web": {
      "ref": "string",
      "digest": "string"
    },
    "backup": {
      "ref": "string",
      "digest": "string"
    },
    "updater": {
      "ref": "string",
      "digest": "string"
    }
  },
  "desiredStateSha256": "string",
  "repairMode": "repair",
  "minFromVersion": "string",
  "notes": "Prefers afternoon sessions.",
  "releasedAt": "2026-03-02T09:00:00.000Z",
  "promotedAt": "2026-03-02T09:00:00.000Z",
  "warnings": [
    "v1_only_newer_than_v2"
  ]
}
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.

POST /api/v1/platform/releases/{releaseId}/promote

Promote a beta release to the stable channel. Platform owner only — this is what reaches every unattended install. Requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
releaseIdrequired
pathstring—

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/releases/97e23b63-97e2-77e2-8e23-97e23b6397e2/promote" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The promoted release.
NameTypeConstraints
idrequired
string (uuid)—
channelrequired
enum: "stable" | "beta"—
versionrequired
string—
imagesrequired
object—
desiredStateSha256required
string | null—
repairModerequired
enum: "repair" | "report"—
minFromVersionrequired
string—
notesrequired
string | null—
releasedAtrequired
string (date-time)—
promotedAtrequired
string (date-time) | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "channel": "stable",
  "version": "string",
  "images": {
    "api": {
      "ref": "string",
      "digest": "string"
    },
    "web": {
      "ref": "string",
      "digest": "string"
    },
    "backup": {
      "ref": "string",
      "digest": "string"
    },
    "updater": {
      "ref": "string",
      "digest": "string"
    }
  },
  "desiredStateSha256": "string",
  "repairMode": "repair",
  "minFromVersion": "string",
  "notes": "Prefers afternoon sessions.",
  "releasedAt": "2026-03-02T09:00:00.000Z",
  "promotedAt": "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.

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/platform/releases/{releaseId}/repair-mode

Set a release's repair mode — the vendor kill switch. `report` makes every install on this release only report damage instead of repairing it. Platform owner only; refused (409) for a release without a v2 manifest. Requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
releaseIdrequired
pathstring—

Request body

NameTypeConstraints
repairModerequired
enum: "repair" | "report"—

Example

json
{
  "repairMode": "repair"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/releases/97e23b63-97e2-77e2-8e23-97e23b6397e2/repair-mode" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "repairMode": "repair"
  }'

Responses

200The release with its new repair mode.
NameTypeConstraints
idrequired
string (uuid)—
channelrequired
enum: "stable" | "beta"—
versionrequired
string—
imagesrequired
object—
desiredStateSha256required
string | null—
repairModerequired
enum: "repair" | "report"—
minFromVersionrequired
string—
notesrequired
string | null—
releasedAtrequired
string (date-time)—
promotedAtrequired
string (date-time) | null—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "channel": "stable",
  "version": "string",
  "images": {
    "api": {
      "ref": "string",
      "digest": "string"
    },
    "web": {
      "ref": "string",
      "digest": "string"
    },
    "backup": {
      "ref": "string",
      "digest": "string"
    },
    "updater": {
      "ref": "string",
      "digest": "string"
    }
  },
  "desiredStateSha256": "string",
  "repairMode": "repair",
  "minFromVersion": "string",
  "notes": "Prefers afternoon sessions.",
  "releasedAt": "2026-03-02T09:00:00.000Z",
  "promotedAt": "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.

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/platform/releases/ci

Publish a release to the beta channel from CI, authenticated by a GitHub Actions OIDC token (no stored secret, see docs/specs/phase11-vendor-console.md §2.5).

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
versionrequired
string—
imagesrequired
object—
desiredState
object—
minFromVersion
string—
notes
string | null1–5000 chars · default: null
releasedAt
string (date-time)—

Example

json
{
  "version": "string",
  "images": {
    "api": {
      "ref": "string",
      "digest": "string"
    },
    "web": {
      "ref": "string",
      "digest": "string"
    },
    "backup": {
      "ref": "string",
      "digest": "string"
    },
    "updater": {
      "ref": "string",
      "digest": "string"
    }
  },
  "desiredState": {
    "sha256": "string",
    "updaterProtocol": 1
  },
  "minFromVersion": "string",
  "notes": null,
  "releasedAt": "2026-03-02T09:00:00.000Z"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/releases/ci" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "version": "string",
    "images": {
      "api": {
        "ref": "string",
        "digest": "string"
      },
      "web": {
        "ref": "string",
        "digest": "string"
      },
      "backup": {
        "ref": "string",
        "digest": "string"
      },
      "updater": {
        "ref": "string",
        "digest": "string"
      }
    },
    "desiredState": {
      "sha256": "string",
      "updaterProtocol": 1
    },
    "minFromVersion": "string",
    "notes": null,
    "releasedAt": "2026-03-02T09:00:00.000Z"
  }'

Responses

201The published release.
NameTypeConstraints
idrequired
string (uuid)—
channelrequired
enum: "stable" | "beta"—
versionrequired
string—
imagesrequired
object—
desiredStateSha256required
string | null—
repairModerequired
enum: "repair" | "report"—
minFromVersionrequired
string—
notesrequired
string | null—
releasedAtrequired
string (date-time)—
promotedAtrequired
string (date-time) | null—
warningsrequired
array<enum: "v1_only_newer_than_v2">—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "channel": "stable",
  "version": "string",
  "images": {
    "api": {
      "ref": "string",
      "digest": "string"
    },
    "web": {
      "ref": "string",
      "digest": "string"
    },
    "backup": {
      "ref": "string",
      "digest": "string"
    },
    "updater": {
      "ref": "string",
      "digest": "string"
    }
  },
  "desiredStateSha256": "string",
  "repairMode": "repair",
  "minFromVersion": "string",
  "notes": "Prefers afternoon sessions.",
  "releasedAt": "2026-03-02T09:00:00.000Z",
  "promotedAt": "2026-03-02T09:00:00.000Z",
  "warnings": [
    "v1_only_newer_than_v2"
  ]
}
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.

GET /api/v1/platform/settings

Platform-wide settings: the global auto-lock toggle and its delay.

Requires authenticationScopeNo additional permission

curl example

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

Responses

200The platform settings.
NameTypeConstraints
autoLockEnabledrequired
boolean—
autoLockAfterDaysrequired
integer0–9007199254740991
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "autoLockEnabled": true,
  "autoLockAfterDays": 0,
  "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.

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.

PATCH /api/v1/platform/settings

Update the global auto-lock policy. With auto-lock off (the default) an expired tenant is never locked automatically. Requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
autoLockEnabled
boolean—
autoLockAfterDays
integer0–365

Example

json
{
  "autoLockEnabled": true,
  "autoLockAfterDays": 0
}

curl example

curl
curl -X PATCH "https://your-institute.example/api/v1/platform/settings" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "autoLockEnabled": true,
    "autoLockAfterDays": 0
  }'

Responses

200The updated platform settings.
NameTypeConstraints
autoLockEnabledrequired
boolean—
autoLockAfterDaysrequired
integer0–9007199254740991
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "autoLockEnabled": true,
  "autoLockAfterDays": 0,
  "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.

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/platform/support/faults

An automatic fault report (closed schema, no free text), signed by the install’s updater sub-key or by the install. A repeat of an open fault only raises its count.

No authenticationScopePublic

Request body

NameTypeConstraints
coderequired
enum: "update_rolled_back" | "update_stuck" | "rollback_failed" | "post_update_drift" | "repair_applied" | "file_unknown_modification" | "env_missing_unrecoverable" | "repair_loop" | "backup_failed" | "backup_missing" | "backup_required_failed" | "offsite_upload_failed" | "offsite_quota_exceeded" | "disk_low" | "docker_daemon_down" | "service_unhealthy" | "registry_auth_failed" | "registry_unreachable" | "certificate_invalid" | "guardian_restored" | "guardian_tampered" | "updater_tampered" | "updater_crashloop" | "pin_unverifiable" | "manifest_bad_signature" | "digest_mismatch" | "desired_state_mismatch" | "protocol_unsupported" | "install_down" | "repair_suppressed" | "service_drift_unrecoverable"—
severityrequired
enum: "info" | "warning" | "critical"—
occurredAtrequired
string (date-time)—
firstSeenAtrequired
string (date-time)—
countrequired
integer1–1000000
versionsrequired
object—
steprequired
enum: "manifest" | "preflight" | "pull" | "backup" | "snapshot" | "apply" | "verify" | "commit" | "rollback" | "converge" | "guardian" | "report" | "adoption" | null | null—
subjectrequired
object | null—
errorClassrequired
enum: "timeout" | "permission_denied" | "not_found" | "auth_refused" | "network_unreachable" | "exit_nonzero" | "health_timeout" | "signature_invalid" | "digest_mismatch" | "out_of_space" | "unknown" | null | null—
exitCoderequired
integer | null-255–255
metricsrequired
object | null—
backupSetIdrequired
string | null—
manifestSha256required
string | null—

Example

json
{
  "code": "update_rolled_back",
  "severity": "info",
  "occurredAt": "2026-03-02T09:00:00.000Z",
  "firstSeenAt": "2026-03-02T09:00:00.000Z",
  "count": 1,
  "versions": {
    "app": "string",
    "updater": "string",
    "guardian": 0
  },
  "step": "manifest",
  "subject": {
    "service": "postgres",
    "path": "docker-compose.yml",
    "envKey": "POSTGRES_PASSWORD"
  },
  "errorClass": "timeout",
  "exitCode": -255,
  "metrics": {
    "diskFreeBytes": 0,
    "diskTotalBytes": 0,
    "setSizeBytes": 0,
    "durationSec": 0
  },
  "backupSetId": "c997a5a0-c997-7997-897a-c997a5a0c997",
  "manifestSha256": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/support/faults" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "code": "update_rolled_back",
    "severity": "info",
    "occurredAt": "2026-03-02T09:00:00.000Z",
    "firstSeenAt": "2026-03-02T09:00:00.000Z",
    "count": 1,
    "versions": {
      "app": "string",
      "updater": "string",
      "guardian": 0
    },
    "step": "manifest",
    "subject": {
      "service": "postgres",
      "path": "docker-compose.yml",
      "envKey": "POSTGRES_PASSWORD"
    },
    "errorClass": "timeout",
    "exitCode": -255,
    "metrics": {
      "diskFreeBytes": 0,
      "diskTotalBytes": 0,
      "setSizeBytes": 0,
      "durationSec": 0
    },
    "backupSetId": "c997a5a0-c997-7997-897a-c997a5a0c997",
    "manifestSha256": "string"
  }'

Responses

200The stored (or coalesced) report.
NameTypeConstraints
idrequired
string (uuid)—
statusrequired
enum: "new" | "acknowledged" | "in_progress" | "resolved" | "declined" | "duplicate"—
createdrequired
boolean—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "status": "new",
  "created": 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.

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/platform/support/faults/unverified

The open intake for a fault when the sender could not be verified. Stored as unverified, never changes any install, at most 16 KiB, 5 per hour per address and 500 a day in all.

No authenticationScopePublic

Request body

NameTypeConstraints
coderequired
enum: "update_rolled_back" | "update_stuck" | "rollback_failed" | "post_update_drift" | "repair_applied" | "file_unknown_modification" | "env_missing_unrecoverable" | "repair_loop" | "backup_failed" | "backup_missing" | "backup_required_failed" | "offsite_upload_failed" | "offsite_quota_exceeded" | "disk_low" | "docker_daemon_down" | "service_unhealthy" | "registry_auth_failed" | "registry_unreachable" | "certificate_invalid" | "guardian_restored" | "guardian_tampered" | "updater_tampered" | "updater_crashloop" | "pin_unverifiable" | "manifest_bad_signature" | "digest_mismatch" | "desired_state_mismatch" | "protocol_unsupported" | "install_down" | "repair_suppressed" | "service_drift_unrecoverable"—
severityrequired
enum: "info" | "warning" | "critical"—
occurredAtrequired
string (date-time)—
firstSeenAtrequired
string (date-time)—
countrequired
integer1–1000000
versionsrequired
object—
steprequired
enum: "manifest" | "preflight" | "pull" | "backup" | "snapshot" | "apply" | "verify" | "commit" | "rollback" | "converge" | "guardian" | "report" | "adoption" | null | null—
subjectrequired
object | null—
errorClassrequired
enum: "timeout" | "permission_denied" | "not_found" | "auth_refused" | "network_unreachable" | "exit_nonzero" | "health_timeout" | "signature_invalid" | "digest_mismatch" | "out_of_space" | "unknown" | null | null—
exitCoderequired
integer | null-255–255
metricsrequired
object | null—
backupSetIdrequired
string | null—
manifestSha256required
string | null—
claimedInstallIdrequired
string (uuid)—

Example

json
{
  "code": "update_rolled_back",
  "severity": "info",
  "occurredAt": "2026-03-02T09:00:00.000Z",
  "firstSeenAt": "2026-03-02T09:00:00.000Z",
  "count": 1,
  "versions": {
    "app": "string",
    "updater": "string",
    "guardian": 0
  },
  "step": "manifest",
  "subject": {
    "service": "postgres",
    "path": "docker-compose.yml",
    "envKey": "POSTGRES_PASSWORD"
  },
  "errorClass": "timeout",
  "exitCode": -255,
  "metrics": {
    "diskFreeBytes": 0,
    "diskTotalBytes": 0,
    "setSizeBytes": 0,
    "durationSec": 0
  },
  "backupSetId": "c997a5a0-c997-7997-897a-c997a5a0c997",
  "manifestSha256": "string",
  "claimedInstallId": "6c98b290-6c98-7c98-898b-6c98b2906c98"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/support/faults/unverified" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "code": "update_rolled_back",
    "severity": "info",
    "occurredAt": "2026-03-02T09:00:00.000Z",
    "firstSeenAt": "2026-03-02T09:00:00.000Z",
    "count": 1,
    "versions": {
      "app": "string",
      "updater": "string",
      "guardian": 0
    },
    "step": "manifest",
    "subject": {
      "service": "postgres",
      "path": "docker-compose.yml",
      "envKey": "POSTGRES_PASSWORD"
    },
    "errorClass": "timeout",
    "exitCode": -255,
    "metrics": {
      "diskFreeBytes": 0,
      "diskTotalBytes": 0,
      "setSizeBytes": 0,
      "durationSec": 0
    },
    "backupSetId": "c997a5a0-c997-7997-897a-c997a5a0c997",
    "manifestSha256": "string",
    "claimedInstallId": "6c98b290-6c98-7c98-898b-6c98b2906c98"
  }'

Responses

200The stored (or coalesced) unverified report.
NameTypeConstraints
idrequired
string (uuid)—
statusrequired
enum: "new" | "acknowledged" | "in_progress" | "resolved" | "declined" | "duplicate"—
createdrequired
boolean—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "status": "new",
  "created": 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.

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/platform/support/reports

List faults, problems and feature requests, newest first — filter by kind, status, verification, severity, code or customer.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25
kind
queryenum: "fault" | "problem" | "feature"—
status
queryenum: "new" | "acknowledged" | "in_progress" | "resolved" | "declined" | "duplicate"—
verification
queryenum: "updater_signed" | "install_signed" | "unverified"—
verified
queryenum: "true" | "false"—
severity
queryenum: "info" | "warning" | "critical"—
code
queryenum: "update_rolled_back" | "update_stuck" | "rollback_failed" | "post_update_drift" | "repair_applied" | "file_unknown_modification" | "env_missing_unrecoverable" | "repair_loop" | "backup_failed" | "backup_missing" | "backup_required_failed" | "offsite_upload_failed" | "offsite_quota_exceeded" | "disk_low" | "docker_daemon_down" | "service_unhealthy" | "registry_auth_failed" | "registry_unreachable" | "certificate_invalid" | "guardian_restored" | "guardian_tampered" | "updater_tampered" | "updater_crashloop" | "pin_unverifiable" | "manifest_bad_signature" | "digest_mismatch" | "desired_state_mismatch" | "protocol_unsupported" | "install_down" | "repair_suppressed" | "service_drift_unrecoverable"—
tenantId
querystring (uuid)—

curl example

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

Responses

200A page of reports.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "kind": "fault",
      "source": "updater",
      "verification": "updater_signed",
      "status": "new",
      "severity": "info",
      "code": "update_rolled_back",
      "category": "login_access",
      "title": "Midterm Exam",
      "tenant": {
        "id": "37386ae0-3738-7738-8386-37386ae03738",
        "name": "Algebra I",
        "slug": "string"
      },
      "installId": "2472867d-2472-7472-8728-2472867d2472",
      "claimedInstallId": "6c98b290-6c98-7c98-898b-6c98b2906c98",
      "serverIp": "string",
      "appVersion": "string",
      "details": {
        "code": "update_rolled_back",
        "severity": "info",
        "occurredAt": "2026-03-02T09:00:00.000Z",
        "firstSeenAt": "2026-03-02T09:00:00.000Z",
        "count": 1,
        "versions": {
          "app": "string",
          "updater": "string",
          "guardian": 0
        },
        "step": "manifest",
        "subject": {
          "service": "postgres",
          "path": "docker-compose.yml",
          "envKey": "POSTGRES_PASSWORD"
        },
        "errorClass": "timeout",
        "exitCode": -255,
        "metrics": {
          "diskFreeBytes": 0,
          "diskTotalBytes": 0,
          "setSizeBytes": 0,
          "durationSec": 0
        },
        "backupSetId": "c997a5a0-c997-7997-897a-c997a5a0c997",
        "manifestSha256": "string"
      },
      "occurrenceCount": -9007199254740991,
      "firstSeenAt": "2026-03-02T09:00:00.000Z",
      "lastSeenAt": "2026-03-02T09:00:00.000Z",
      "duplicateOf": "8e5c1847-8e5c-7e5c-85c1-8e5c18478e5c",
      "resolvedAt": "2026-03-02T09:00:00.000Z",
      "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.

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/platform/support/reports/{id}

One report with its structured fields, the owner’s text and the vendor’s replies.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/support/reports/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The report.
NameTypeConstraints
idrequired
string (uuid)—
kindrequired
enum: "fault" | "problem" | "feature"—
sourcerequired
enum: "updater" | "guardian" | "install_ui" | "unverified_intake"—
verificationrequired
enum: "updater_signed" | "install_signed" | "unverified"—
statusrequired
enum: "new" | "acknowledged" | "in_progress" | "resolved" | "declined" | "duplicate"—
severityrequired
enum: "info" | "warning" | "critical" | null | null—
coderequired
enum: "update_rolled_back" | "update_stuck" | "rollback_failed" | "post_update_drift" | "repair_applied" | "file_unknown_modification" | "env_missing_unrecoverable" | "repair_loop" | "backup_failed" | "backup_missing" | "backup_required_failed" | "offsite_upload_failed" | "offsite_quota_exceeded" | "disk_low" | "docker_daemon_down" | "service_unhealthy" | "registry_auth_failed" | "registry_unreachable" | "certificate_invalid" | "guardian_restored" | "guardian_tampered" | "updater_tampered" | "updater_crashloop" | "pin_unverifiable" | "manifest_bad_signature" | "digest_mismatch" | "desired_state_mismatch" | "protocol_unsupported" | "install_down" | "repair_suppressed" | "service_drift_unrecoverable" | null | null—
categoryrequired
enum: "login_access" | "people" | "attendance" | "billing" | "courses_content" | "notifications" | "updates_backups" | "performance" | "other" | null | null—
titlerequired
string | null—
tenantrequired
object | null—
installIdrequired
string | null—
claimedInstallIdrequired
string (uuid) | null—
serverIprequired
string | null—
appVersionrequired
string | null—
detailsrequired
object | null—
occurrenceCountrequired
integer-9007199254740991–9007199254740991
firstSeenAtrequired
string (date-time)—
lastSeenAtrequired
string (date-time)—
duplicateOfrequired
string (uuid) | null—
resolvedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
bodyrequired
string | null—
messagesrequired
array<object>—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "kind": "fault",
  "source": "updater",
  "verification": "updater_signed",
  "status": "new",
  "severity": "info",
  "code": "update_rolled_back",
  "category": "login_access",
  "title": "Midterm Exam",
  "tenant": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "name": "Algebra I",
    "slug": "string"
  },
  "installId": "2472867d-2472-7472-8728-2472867d2472",
  "claimedInstallId": "6c98b290-6c98-7c98-898b-6c98b2906c98",
  "serverIp": "string",
  "appVersion": "string",
  "details": {
    "code": "update_rolled_back",
    "severity": "info",
    "occurredAt": "2026-03-02T09:00:00.000Z",
    "firstSeenAt": "2026-03-02T09:00:00.000Z",
    "count": 1,
    "versions": {
      "app": "string",
      "updater": "string",
      "guardian": 0
    },
    "step": "manifest",
    "subject": {
      "service": "postgres",
      "path": "docker-compose.yml",
      "envKey": "POSTGRES_PASSWORD"
    },
    "errorClass": "timeout",
    "exitCode": -255,
    "metrics": {
      "diskFreeBytes": 0,
      "diskTotalBytes": 0,
      "setSizeBytes": 0,
      "durationSec": 0
    },
    "backupSetId": "c997a5a0-c997-7997-897a-c997a5a0c997",
    "manifestSha256": "string"
  },
  "occurrenceCount": -9007199254740991,
  "firstSeenAt": "2026-03-02T09:00:00.000Z",
  "lastSeenAt": "2026-03-02T09:00:00.000Z",
  "duplicateOf": "8e5c1847-8e5c-7e5c-85c1-8e5c18478e5c",
  "resolvedAt": "2026-03-02T09:00:00.000Z",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z",
  "body": "string",
  "messages": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "author": "vendor",
      "body": "string",
      "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.

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/platform/support/reports/{id}/duplicate

Mark a report as a duplicate of an earlier one of the same kind. Audited.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
duplicateOfrequired
string (uuid)—

Example

json
{
  "duplicateOf": "8e5c1847-8e5c-7e5c-85c1-8e5c18478e5c"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/support/reports/37386ae0-3738-7738-8386-37386ae03738/duplicate" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "duplicateOf": "8e5c1847-8e5c-7e5c-85c1-8e5c18478e5c"
  }'

Responses

200The report, now a duplicate.
NameTypeConstraints
idrequired
string (uuid)—
kindrequired
enum: "fault" | "problem" | "feature"—
sourcerequired
enum: "updater" | "guardian" | "install_ui" | "unverified_intake"—
verificationrequired
enum: "updater_signed" | "install_signed" | "unverified"—
statusrequired
enum: "new" | "acknowledged" | "in_progress" | "resolved" | "declined" | "duplicate"—
severityrequired
enum: "info" | "warning" | "critical" | null | null—
coderequired
enum: "update_rolled_back" | "update_stuck" | "rollback_failed" | "post_update_drift" | "repair_applied" | "file_unknown_modification" | "env_missing_unrecoverable" | "repair_loop" | "backup_failed" | "backup_missing" | "backup_required_failed" | "offsite_upload_failed" | "offsite_quota_exceeded" | "disk_low" | "docker_daemon_down" | "service_unhealthy" | "registry_auth_failed" | "registry_unreachable" | "certificate_invalid" | "guardian_restored" | "guardian_tampered" | "updater_tampered" | "updater_crashloop" | "pin_unverifiable" | "manifest_bad_signature" | "digest_mismatch" | "desired_state_mismatch" | "protocol_unsupported" | "install_down" | "repair_suppressed" | "service_drift_unrecoverable" | null | null—
categoryrequired
enum: "login_access" | "people" | "attendance" | "billing" | "courses_content" | "notifications" | "updates_backups" | "performance" | "other" | null | null—
titlerequired
string | null—
tenantrequired
object | null—
installIdrequired
string | null—
claimedInstallIdrequired
string (uuid) | null—
serverIprequired
string | null—
appVersionrequired
string | null—
detailsrequired
object | null—
occurrenceCountrequired
integer-9007199254740991–9007199254740991
firstSeenAtrequired
string (date-time)—
lastSeenAtrequired
string (date-time)—
duplicateOfrequired
string (uuid) | null—
resolvedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "kind": "fault",
  "source": "updater",
  "verification": "updater_signed",
  "status": "new",
  "severity": "info",
  "code": "update_rolled_back",
  "category": "login_access",
  "title": "Midterm Exam",
  "tenant": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "name": "Algebra I",
    "slug": "string"
  },
  "installId": "2472867d-2472-7472-8728-2472867d2472",
  "claimedInstallId": "6c98b290-6c98-7c98-898b-6c98b2906c98",
  "serverIp": "string",
  "appVersion": "string",
  "details": {
    "code": "update_rolled_back",
    "severity": "info",
    "occurredAt": "2026-03-02T09:00:00.000Z",
    "firstSeenAt": "2026-03-02T09:00:00.000Z",
    "count": 1,
    "versions": {
      "app": "string",
      "updater": "string",
      "guardian": 0
    },
    "step": "manifest",
    "subject": {
      "service": "postgres",
      "path": "docker-compose.yml",
      "envKey": "POSTGRES_PASSWORD"
    },
    "errorClass": "timeout",
    "exitCode": -255,
    "metrics": {
      "diskFreeBytes": 0,
      "diskTotalBytes": 0,
      "setSizeBytes": 0,
      "durationSec": 0
    },
    "backupSetId": "c997a5a0-c997-7997-897a-c997a5a0c997",
    "manifestSha256": "string"
  },
  "occurrenceCount": -9007199254740991,
  "firstSeenAt": "2026-03-02T09:00:00.000Z",
  "lastSeenAt": "2026-03-02T09:00:00.000Z",
  "duplicateOf": "8e5c1847-8e5c-7e5c-85c1-8e5c18478e5c",
  "resolvedAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/support/reports/{id}/reply

Reply to a problem or a feature request; the institute’s owner sees the reply on their install. Audited.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
bodyrequired
string1–4000 chars

Example

json
{
  "body": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/support/reports/37386ae0-3738-7738-8386-37386ae03738/reply" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "body": "string"
  }'

Responses

201The report with the new reply.
NameTypeConstraints
idrequired
string (uuid)—
kindrequired
enum: "fault" | "problem" | "feature"—
sourcerequired
enum: "updater" | "guardian" | "install_ui" | "unverified_intake"—
verificationrequired
enum: "updater_signed" | "install_signed" | "unverified"—
statusrequired
enum: "new" | "acknowledged" | "in_progress" | "resolved" | "declined" | "duplicate"—
severityrequired
enum: "info" | "warning" | "critical" | null | null—
coderequired
enum: "update_rolled_back" | "update_stuck" | "rollback_failed" | "post_update_drift" | "repair_applied" | "file_unknown_modification" | "env_missing_unrecoverable" | "repair_loop" | "backup_failed" | "backup_missing" | "backup_required_failed" | "offsite_upload_failed" | "offsite_quota_exceeded" | "disk_low" | "docker_daemon_down" | "service_unhealthy" | "registry_auth_failed" | "registry_unreachable" | "certificate_invalid" | "guardian_restored" | "guardian_tampered" | "updater_tampered" | "updater_crashloop" | "pin_unverifiable" | "manifest_bad_signature" | "digest_mismatch" | "desired_state_mismatch" | "protocol_unsupported" | "install_down" | "repair_suppressed" | "service_drift_unrecoverable" | null | null—
categoryrequired
enum: "login_access" | "people" | "attendance" | "billing" | "courses_content" | "notifications" | "updates_backups" | "performance" | "other" | null | null—
titlerequired
string | null—
tenantrequired
object | null—
installIdrequired
string | null—
claimedInstallIdrequired
string (uuid) | null—
serverIprequired
string | null—
appVersionrequired
string | null—
detailsrequired
object | null—
occurrenceCountrequired
integer-9007199254740991–9007199254740991
firstSeenAtrequired
string (date-time)—
lastSeenAtrequired
string (date-time)—
duplicateOfrequired
string (uuid) | null—
resolvedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
bodyrequired
string | null—
messagesrequired
array<object>—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "kind": "fault",
  "source": "updater",
  "verification": "updater_signed",
  "status": "new",
  "severity": "info",
  "code": "update_rolled_back",
  "category": "login_access",
  "title": "Midterm Exam",
  "tenant": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "name": "Algebra I",
    "slug": "string"
  },
  "installId": "2472867d-2472-7472-8728-2472867d2472",
  "claimedInstallId": "6c98b290-6c98-7c98-898b-6c98b2906c98",
  "serverIp": "string",
  "appVersion": "string",
  "details": {
    "code": "update_rolled_back",
    "severity": "info",
    "occurredAt": "2026-03-02T09:00:00.000Z",
    "firstSeenAt": "2026-03-02T09:00:00.000Z",
    "count": 1,
    "versions": {
      "app": "string",
      "updater": "string",
      "guardian": 0
    },
    "step": "manifest",
    "subject": {
      "service": "postgres",
      "path": "docker-compose.yml",
      "envKey": "POSTGRES_PASSWORD"
    },
    "errorClass": "timeout",
    "exitCode": -255,
    "metrics": {
      "diskFreeBytes": 0,
      "diskTotalBytes": 0,
      "setSizeBytes": 0,
      "durationSec": 0
    },
    "backupSetId": "c997a5a0-c997-7997-897a-c997a5a0c997",
    "manifestSha256": "string"
  },
  "occurrenceCount": -9007199254740991,
  "firstSeenAt": "2026-03-02T09:00:00.000Z",
  "lastSeenAt": "2026-03-02T09:00:00.000Z",
  "duplicateOf": "8e5c1847-8e5c-7e5c-85c1-8e5c18478e5c",
  "resolvedAt": "2026-03-02T09:00:00.000Z",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z",
  "body": "string",
  "messages": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "author": "vendor",
      "body": "string",
      "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.

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/platform/support/reports/{id}/status

Acknowledge, start, resolve or decline a report. Audited.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
statusrequired
enum: "new" | "acknowledged" | "in_progress" | "resolved" | "declined" | "duplicate"—

Example

json
{
  "status": "new"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/support/reports/37386ae0-3738-7738-8386-37386ae03738/status" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "status": "new"
  }'

Responses

200The report with its new status.
NameTypeConstraints
idrequired
string (uuid)—
kindrequired
enum: "fault" | "problem" | "feature"—
sourcerequired
enum: "updater" | "guardian" | "install_ui" | "unverified_intake"—
verificationrequired
enum: "updater_signed" | "install_signed" | "unverified"—
statusrequired
enum: "new" | "acknowledged" | "in_progress" | "resolved" | "declined" | "duplicate"—
severityrequired
enum: "info" | "warning" | "critical" | null | null—
coderequired
enum: "update_rolled_back" | "update_stuck" | "rollback_failed" | "post_update_drift" | "repair_applied" | "file_unknown_modification" | "env_missing_unrecoverable" | "repair_loop" | "backup_failed" | "backup_missing" | "backup_required_failed" | "offsite_upload_failed" | "offsite_quota_exceeded" | "disk_low" | "docker_daemon_down" | "service_unhealthy" | "registry_auth_failed" | "registry_unreachable" | "certificate_invalid" | "guardian_restored" | "guardian_tampered" | "updater_tampered" | "updater_crashloop" | "pin_unverifiable" | "manifest_bad_signature" | "digest_mismatch" | "desired_state_mismatch" | "protocol_unsupported" | "install_down" | "repair_suppressed" | "service_drift_unrecoverable" | null | null—
categoryrequired
enum: "login_access" | "people" | "attendance" | "billing" | "courses_content" | "notifications" | "updates_backups" | "performance" | "other" | null | null—
titlerequired
string | null—
tenantrequired
object | null—
installIdrequired
string | null—
claimedInstallIdrequired
string (uuid) | null—
serverIprequired
string | null—
appVersionrequired
string | null—
detailsrequired
object | null—
occurrenceCountrequired
integer-9007199254740991–9007199254740991
firstSeenAtrequired
string (date-time)—
lastSeenAtrequired
string (date-time)—
duplicateOfrequired
string (uuid) | null—
resolvedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "kind": "fault",
  "source": "updater",
  "verification": "updater_signed",
  "status": "new",
  "severity": "info",
  "code": "update_rolled_back",
  "category": "login_access",
  "title": "Midterm Exam",
  "tenant": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "name": "Algebra I",
    "slug": "string"
  },
  "installId": "2472867d-2472-7472-8728-2472867d2472",
  "claimedInstallId": "6c98b290-6c98-7c98-898b-6c98b2906c98",
  "serverIp": "string",
  "appVersion": "string",
  "details": {
    "code": "update_rolled_back",
    "severity": "info",
    "occurredAt": "2026-03-02T09:00:00.000Z",
    "firstSeenAt": "2026-03-02T09:00:00.000Z",
    "count": 1,
    "versions": {
      "app": "string",
      "updater": "string",
      "guardian": 0
    },
    "step": "manifest",
    "subject": {
      "service": "postgres",
      "path": "docker-compose.yml",
      "envKey": "POSTGRES_PASSWORD"
    },
    "errorClass": "timeout",
    "exitCode": -255,
    "metrics": {
      "diskFreeBytes": 0,
      "diskTotalBytes": 0,
      "setSizeBytes": 0,
      "durationSec": 0
    },
    "backupSetId": "c997a5a0-c997-7997-897a-c997a5a0c997",
    "manifestSha256": "string"
  },
  "occurrenceCount": -9007199254740991,
  "firstSeenAt": "2026-03-02T09:00:00.000Z",
  "lastSeenAt": "2026-03-02T09:00:00.000Z",
  "duplicateOf": "8e5c1847-8e5c-7e5c-85c1-8e5c18478e5c",
  "resolvedAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/support/reports/summary

What is new, split the way the console tabs are — the numbers behind the sidebar badge.

Requires authenticationScopeNo additional permission

curl example

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

Responses

200Counts of new reports.
NameTypeConstraints
newCriticalFaultsrequired
integer-9007199254740991–9007199254740991
newFaultsrequired
integer-9007199254740991–9007199254740991
newProblemsrequired
integer-9007199254740991–9007199254740991
newFeaturesrequired
integer-9007199254740991–9007199254740991
newUnverifiedrequired
integer-9007199254740991–9007199254740991
json
{
  "newCriticalFaults": -9007199254740991,
  "newFaults": -9007199254740991,
  "newProblems": -9007199254740991,
  "newFeatures": -9007199254740991,
  "newUnverified": -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.

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/platform/support/requests

This install’s customer’s own problem and feature requests with their status and the vendor’s replies, newest first; ?since= narrows to what changed. Never another customer’s.

No authenticationScopePublic

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25
since
querystring (date-time)—

curl example

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

Responses

200A page of requests.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "clientRef": "97cc9cef-97cc-77cc-8cc9-97cc9cef97cc",
      "kind": "problem",
      "category": "login_access",
      "title": "Midterm Exam",
      "status": "new",
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z",
      "replies": [
        {
          "id": "37386ae0-3738-7738-8386-37386ae03738",
          "body": "string",
          "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.

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/platform/support/requests

A problem report or a feature request an institute’s owner wrote, signed by the install. Idempotent on clientRef; carries no author name or email.

No authenticationScopePublic

Request body

NameTypeConstraints
clientRefrequired
string (uuid)—
kindrequired
enum: "problem" | "feature"—
categoryrequired
enum: "login_access" | "people" | "attendance" | "billing" | "courses_content" | "notifications" | "updates_backups" | "performance" | "other"—
titlerequired
string1–120 chars
textrequired
string1–4000 chars

Example

json
{
  "clientRef": "97cc9cef-97cc-77cc-8cc9-97cc9cef97cc",
  "kind": "problem",
  "category": "login_access",
  "title": "Midterm Exam",
  "text": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/support/requests" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "clientRef": "97cc9cef-97cc-77cc-8cc9-97cc9cef97cc",
    "kind": "problem",
    "category": "login_access",
    "title": "Midterm Exam",
    "text": "string"
  }'

Responses

200The stored report.
NameTypeConstraints
idrequired
string (uuid)—
statusrequired
enum: "new" | "acknowledged" | "in_progress" | "resolved" | "declined" | "duplicate"—
createdrequired
boolean—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "status": "new",
  "created": 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.

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/platform/tenants

List tenants (cursor-paginated, optional name search and status filter).

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25
search
querystring1–200 chars
status
queryenum: "active" | "suspended" | "archived" | "deleted"—

curl example

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

Responses

200A page of tenants.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "slug": "string",
      "name": "Algebra I",
      "status": "active",
      "defaultLocale": "ar",
      "timezone": "string",
      "currency": "USD",
      "deploymentMode": "cloud",
      "autoLockOverride": "inherit",
      "lockedAt": "2026-03-02T09:00:00.000Z",
      "lockedReason": "string",
      "lockedByPlatformAdminId": "f0abde01-f0ab-70ab-8abd-f0abde01f0ab",
      "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
      "deletedAt": "2026-03-02T09:00:00.000Z",
      "deletedName": "string",
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z",
      "subscriptionStatus": "trialing"
    }
  ],
  "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.

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/platform/tenants

Create a tenant: seeds its default branch/roles, primary subdomain, trial subscription, and emails the owner an invitation.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
slugrequired
string3–40 chars
namerequired
string1–200 chars
defaultLocalerequired
enum: "ar" | "en" | "he"—
timezone
string1–100 chars
currency
string3–3 chars
deploymentMode
enum: "cloud" | "self_hosted"—
planrequired
string1–50 chars
trialDaysrequired
integer0–365
ownerEmailrequired
string (email)—

Example

json
{
  "slug": "string",
  "name": "Algebra I",
  "defaultLocale": "ar",
  "timezone": "string",
  "currency": "USD",
  "deploymentMode": "cloud",
  "plan": "string",
  "trialDays": 0,
  "ownerEmail": "ada.lovelace@example.com"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "slug": "string",
    "name": "Algebra I",
    "defaultLocale": "ar",
    "timezone": "string",
    "currency": "USD",
    "deploymentMode": "cloud",
    "plan": "string",
    "trialDays": 0,
    "ownerEmail": "ada.lovelace@example.com"
  }'

Responses

201The newly created tenant.
NameTypeConstraints
tenantrequired
object—
domainrequired
string—
invitationIdrequired
string (uuid)—
portalOwnerrequired
unknown—
json
{
  "tenant": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "slug": "string",
    "name": "Algebra I",
    "status": "active",
    "defaultLocale": "ar",
    "timezone": "string",
    "currency": "USD",
    "deploymentMode": "cloud",
    "autoLockOverride": "inherit",
    "lockedAt": "2026-03-02T09:00:00.000Z",
    "lockedReason": "string",
    "lockedByPlatformAdminId": "f0abde01-f0ab-70ab-8abd-f0abde01f0ab",
    "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
    "deletedAt": "2026-03-02T09:00:00.000Z",
    "deletedName": "string",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  },
  "domain": "string",
  "invitationId": "00637243-0063-7063-8637-006372430063",
  "portalOwner": "value"
}
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.

GET /api/v1/platform/tenants/{id}

Tenant detail: subscription, domains, member/branch counts.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The tenant, its subscription, domains, and counts.
NameTypeConstraints
tenantrequired
object—
subscriptionrequired
object | null—
domainsrequired
array<object>—
countsrequired
object—
endedAtrequired
string (date-time) | null—
portalOwnerrequired
unknown—
json
{
  "tenant": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "slug": "string",
    "name": "Algebra I",
    "status": "active",
    "defaultLocale": "ar",
    "timezone": "string",
    "currency": "USD",
    "deploymentMode": "cloud",
    "autoLockOverride": "inherit",
    "lockedAt": "2026-03-02T09:00:00.000Z",
    "lockedReason": "string",
    "lockedByPlatformAdminId": "f0abde01-f0ab-70ab-8abd-f0abde01f0ab",
    "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
    "deletedAt": "2026-03-02T09:00:00.000Z",
    "deletedName": "string",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  },
  "subscription": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "planId": "744bf63f-744b-744b-84bf-744bf63f744b",
    "status": "trialing",
    "currentPeriodStart": "2026-03-02T09:00:00.000Z",
    "currentPeriodEnd": "2026-03-02T09:00:00.000Z",
    "cancelledAt": "2026-03-02T09:00:00.000Z",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  },
  "domains": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
      "domain": "string",
      "kind": "subdomain",
      "verificationToken": "string",
      "verifiedAt": "2026-03-02T09:00:00.000Z",
      "isPrimary": true,
      "createdAt": "2026-03-02T09:00:00.000Z",
      "updatedAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "counts": {
    "members": -9007199254740991,
    "branches": -9007199254740991
  },
  "endedAt": "2026-03-02T09:00:00.000Z",
  "portalOwner": "value"
}
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.

PATCH /api/v1/platform/tenants/{id}

Update a tenant’s name, locale, timezone, currency, or per-tenant auto-lock override. Changing the auto-lock override is owner only.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
name
string1–200 chars
defaultLocale
enum: "ar" | "en" | "he"—
timezone
string1–100 chars
currency
string3–3 chars
autoLockOverride
enum: "inherit" | "on" | "off"—

Example

json
{
  "name": "Algebra I",
  "defaultLocale": "ar",
  "timezone": "string",
  "currency": "USD",
  "autoLockOverride": "inherit"
}

curl example

curl
curl -X PATCH "https://your-institute.example/api/v1/platform/tenants/37386ae0-3738-7738-8386-37386ae03738" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Algebra I",
    "defaultLocale": "ar",
    "timezone": "string",
    "currency": "USD",
    "autoLockOverride": "inherit"
  }'

Responses

200The updated tenant.
NameTypeConstraints
idrequired
string (uuid)—
slugrequired
string—
namerequired
string—
statusrequired
enum: "active" | "suspended" | "archived" | "deleted"—
defaultLocalerequired
enum: "ar" | "en" | "he"—
timezonerequired
string—
currencyrequired
string3–3 chars
deploymentModerequired
enum: "cloud" | "self_hosted"—
autoLockOverriderequired
enum: "inherit" | "on" | "off"—
lockedAtrequired
string (date-time) | null—
lockedReasonrequired
string | null—
lockedByPlatformAdminIdrequired
string (uuid) | null—
createdByrequired
string (uuid) | null—
deletedAtrequired
string (date-time) | null—
deletedNamerequired
string | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "slug": "string",
  "name": "Algebra I",
  "status": "active",
  "defaultLocale": "ar",
  "timezone": "string",
  "currency": "USD",
  "deploymentMode": "cloud",
  "autoLockOverride": "inherit",
  "lockedAt": "2026-03-02T09:00:00.000Z",
  "lockedReason": "string",
  "lockedByPlatformAdminId": "f0abde01-f0ab-70ab-8abd-f0abde01f0ab",
  "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
  "deletedAt": "2026-03-02T09:00:00.000Z",
  "deletedName": "string",
  "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.

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/platform/tenants/{id}/delete

Delete an ended (archived) customer’s data: every row of the tenant in every tenant-scoped table, its files in object storage, its domains, and the users who belong to no other tenant. Kept: audit log, licences, subscriptions, install tokens, registry credentials, relay account and ledger, customer requests — and the tenant row as a tombstone (status `deleted`, slug renamed free). `confirmName` must equal the tenant’s name exactly. 409 unless the tenant is archived. Owner only; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
confirmNamerequired
string1–200 chars

Example

json
{
  "confirmName": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/37386ae0-3738-7738-8386-37386ae03738/delete" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "confirmName": "string"
  }'

Responses

200The tombstone, what was removed, and the outcome of the storage cleanup.
NameTypeConstraints
tenantrequired
object—
deletedRowsrequired
integer0–9007199254740991
deletedUsersrequired
integer0–9007199254740991
storageCleanuprequired
enum: "done" | "failed"—
json
{
  "tenant": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "slug": "string",
    "name": "Algebra I",
    "status": "active",
    "defaultLocale": "ar",
    "timezone": "string",
    "currency": "USD",
    "deploymentMode": "cloud",
    "autoLockOverride": "inherit",
    "lockedAt": "2026-03-02T09:00:00.000Z",
    "lockedReason": "string",
    "lockedByPlatformAdminId": "f0abde01-f0ab-70ab-8abd-f0abde01f0ab",
    "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
    "deletedAt": "2026-03-02T09:00:00.000Z",
    "deletedName": "string",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  },
  "deletedRows": 0,
  "deletedUsers": 0,
  "storageCleanup": "done"
}
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.

POST /api/v1/platform/tenants/{id}/end

End a customer: revokes every licence and registry credential, closes the relay account, and archives the tenant. Nothing is deleted. Owner only; requires a step-up MFA check within the last 10 minutes; idempotent.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The tenant, now archived.
NameTypeConstraints
idrequired
string (uuid)—
slugrequired
string—
namerequired
string—
statusrequired
enum: "active" | "suspended" | "archived" | "deleted"—
defaultLocalerequired
enum: "ar" | "en" | "he"—
timezonerequired
string—
currencyrequired
string3–3 chars
deploymentModerequired
enum: "cloud" | "self_hosted"—
autoLockOverriderequired
enum: "inherit" | "on" | "off"—
lockedAtrequired
string (date-time) | null—
lockedReasonrequired
string | null—
lockedByPlatformAdminIdrequired
string (uuid) | null—
createdByrequired
string (uuid) | null—
deletedAtrequired
string (date-time) | null—
deletedNamerequired
string | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "slug": "string",
  "name": "Algebra I",
  "status": "active",
  "defaultLocale": "ar",
  "timezone": "string",
  "currency": "USD",
  "deploymentMode": "cloud",
  "autoLockOverride": "inherit",
  "lockedAt": "2026-03-02T09:00:00.000Z",
  "lockedReason": "string",
  "lockedByPlatformAdminId": "f0abde01-f0ab-70ab-8abd-f0abde01f0ab",
  "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
  "deletedAt": "2026-03-02T09:00:00.000Z",
  "deletedName": "string",
  "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.

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/platform/tenants/{id}/impersonate

Issue a 10-minute access token for the tenant’s owner membership (audited). Owner only; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
reasonrequired
string1–500 chars

Example

json
{
  "reason": "Requested by guardian."
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/37386ae0-3738-7738-8386-37386ae03738/impersonate" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "reason": "Requested by guardian."
  }'

Responses

200A short-lived access token scoped to the tenant’s owner.
NameTypeConstraints
accessTokenrequired
string—
expiresInSecrequired
integer-9007199254740991–9007199254740991
json
{
  "accessToken": "string",
  "expiresInSec": -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.

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/platform/tenants/{id}/link-portal-owner

Link or invite a portal owner for an institute that has none (docs/specs/phase17-unified-login.md §5.3, closes K-68). Owner only; requires a step-up MFA check within the last 10 minutes; refuses if the institute already has a portal owner, or if the email already owns a different institute.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
emailrequired
string (email)—

Example

json
{
  "email": "ada.lovelace@example.com"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/37386ae0-3738-7738-8386-37386ae03738/link-portal-owner" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "email": "ada.lovelace@example.com"
  }'

Responses

200The resulting portal-owner status (linked or invited).
json
"value"
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.

POST /api/v1/platform/tenants/{id}/lock

Lock a tenant explicitly: its subscription becomes suspended and every member request gets 402 (except the allowlist). Expiry alone never does this.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

Request body

NameTypeConstraints
reasonrequired
string1–500 chars

Example

json
{
  "reason": "Requested by guardian."
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/37386ae0-3738-7738-8386-37386ae03738/lock" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "reason": "Requested by guardian."
  }'

Responses

200The subscription, now locked.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
planIdrequired
string (uuid)—
statusrequired
enum: "trialing" | "active" | "expired" | "suspended" | "cancelled"—
currentPeriodStartrequired
string (date-time)—
currentPeriodEndrequired
string (date-time)—
cancelledAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "planId": "744bf63f-744b-744b-84bf-744bf63f744b",
  "status": "trialing",
  "currentPeriodStart": "2026-03-02T09:00:00.000Z",
  "currentPeriodEnd": "2026-03-02T09:00:00.000Z",
  "cancelledAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/tenants/{id}/reactivate

Reactivate a suspended tenant. Owner only.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The tenant, now active.
NameTypeConstraints
idrequired
string (uuid)—
slugrequired
string—
namerequired
string—
statusrequired
enum: "active" | "suspended" | "archived" | "deleted"—
defaultLocalerequired
enum: "ar" | "en" | "he"—
timezonerequired
string—
currencyrequired
string3–3 chars
deploymentModerequired
enum: "cloud" | "self_hosted"—
autoLockOverriderequired
enum: "inherit" | "on" | "off"—
lockedAtrequired
string (date-time) | null—
lockedReasonrequired
string | null—
lockedByPlatformAdminIdrequired
string (uuid) | null—
createdByrequired
string (uuid) | null—
deletedAtrequired
string (date-time) | null—
deletedNamerequired
string | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "slug": "string",
  "name": "Algebra I",
  "status": "active",
  "defaultLocale": "ar",
  "timezone": "string",
  "currency": "USD",
  "deploymentMode": "cloud",
  "autoLockOverride": "inherit",
  "lockedAt": "2026-03-02T09:00:00.000Z",
  "lockedReason": "string",
  "lockedByPlatformAdminId": "f0abde01-f0ab-70ab-8abd-f0abde01f0ab",
  "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
  "deletedAt": "2026-03-02T09:00:00.000Z",
  "deletedName": "string",
  "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.

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/platform/tenants/{id}/suspend

Suspend a tenant. Every member request then gets 402 (except the allowlist). Owner only.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The tenant, now suspended.
NameTypeConstraints
idrequired
string (uuid)—
slugrequired
string—
namerequired
string—
statusrequired
enum: "active" | "suspended" | "archived" | "deleted"—
defaultLocalerequired
enum: "ar" | "en" | "he"—
timezonerequired
string—
currencyrequired
string3–3 chars
deploymentModerequired
enum: "cloud" | "self_hosted"—
autoLockOverriderequired
enum: "inherit" | "on" | "off"—
lockedAtrequired
string (date-time) | null—
lockedReasonrequired
string | null—
lockedByPlatformAdminIdrequired
string (uuid) | null—
createdByrequired
string (uuid) | null—
deletedAtrequired
string (date-time) | null—
deletedNamerequired
string | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "slug": "string",
  "name": "Algebra I",
  "status": "active",
  "defaultLocale": "ar",
  "timezone": "string",
  "currency": "USD",
  "deploymentMode": "cloud",
  "autoLockOverride": "inherit",
  "lockedAt": "2026-03-02T09:00:00.000Z",
  "lockedReason": "string",
  "lockedByPlatformAdminId": "f0abde01-f0ab-70ab-8abd-f0abde01f0ab",
  "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
  "deletedAt": "2026-03-02T09:00:00.000Z",
  "deletedName": "string",
  "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.

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/platform/tenants/{id}/unarchive

Restore an ended (archived) customer: the tenant becomes active and its members can sign in again; a relay account that "end" closed is reopened. Licences, registry credentials and install commands stay revoked — re-issue them from the Customer tab (`reissueRequired`). 409 unless the tenant is archived. Owner only; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The tenant, now active.
NameTypeConstraints
tenantrequired
object—
relayAccountrequired
enum: "reopened" | "unchanged" | "none"—
reissueRequiredrequired
array<enum: "licenses" | "registry_credentials" | "install_command">—
json
{
  "tenant": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "slug": "string",
    "name": "Algebra I",
    "status": "active",
    "defaultLocale": "ar",
    "timezone": "string",
    "currency": "USD",
    "deploymentMode": "cloud",
    "autoLockOverride": "inherit",
    "lockedAt": "2026-03-02T09:00:00.000Z",
    "lockedReason": "string",
    "lockedByPlatformAdminId": "f0abde01-f0ab-70ab-8abd-f0abde01f0ab",
    "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
    "deletedAt": "2026-03-02T09:00:00.000Z",
    "deletedName": "string",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  },
  "relayAccount": "reopened",
  "reissueRequired": [
    "licenses"
  ]
}
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.

POST /api/v1/platform/tenants/{id}/unlock

Unlock a tenant: back to active, or expired if the paid period has run out.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The subscription, now unlocked.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
planIdrequired
string (uuid)—
statusrequired
enum: "trialing" | "active" | "expired" | "suspended" | "cancelled"—
currentPeriodStartrequired
string (date-time)—
currentPeriodEndrequired
string (date-time)—
cancelledAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "planId": "744bf63f-744b-744b-84bf-744bf63f744b",
  "status": "trialing",
  "currentPeriodStart": "2026-03-02T09:00:00.000Z",
  "currentPeriodEnd": "2026-03-02T09:00:00.000Z",
  "cancelledAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/tenants/{tenantId}/bundle

Download the customer install bundle (docker-compose.yml, .env.example, Caddyfile, README-FIRST.md) as a tar.gz for a published version.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—
versionrequired
querystring—

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/bundle?version=string" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The bundle, as a gzip-compressed tar stream.

The API's OpenAPI spec doesn't declare a typed schema for this response yet — it's still JSON, just not documented here.

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.

POST /api/v1/platform/tenants/{tenantId}/domains

Add a custom domain (CNAME). Returns a verification token to publish as a DNS TXT record.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

Request body

NameTypeConstraints
domainrequired
string3–253 chars

Example

json
{
  "domain": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/domains" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "domain": "string"
  }'

Responses

201The newly added (unverified) domain.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
domainrequired
string—
kindrequired
enum: "subdomain" | "custom"—
verificationTokenrequired
string—
verifiedAtrequired
string (date-time) | null—
isPrimaryrequired
boolean—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "domain": "string",
  "kind": "subdomain",
  "verificationToken": "string",
  "verifiedAt": "2026-03-02T09:00:00.000Z",
  "isPrimary": 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.

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/platform/tenants/{tenantId}/domains/{domainId}

Remove a custom domain (the primary domain cannot be removed).

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—
domainIdrequired
pathstring—

curl example

curl
curl -X DELETE "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/domains/f5472324-f547-7547-8472-f5472324f547" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The domain was removed.
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.

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/platform/tenants/{tenantId}/domains/{domainId}/verify

Verify a custom domain by DNS TXT lookup at `_ims-verify.<domain>`.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—
domainIdrequired
pathstring—

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/domains/f5472324-f547-7547-8472-f5472324f547/verify" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The now-verified domain.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
domainrequired
string—
kindrequired
enum: "subdomain" | "custom"—
verificationTokenrequired
string—
verifiedAtrequired
string (date-time) | null—
isPrimaryrequired
boolean—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "domain": "string",
  "kind": "subdomain",
  "verificationToken": "string",
  "verifiedAt": "2026-03-02T09:00:00.000Z",
  "isPrimary": 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.

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/platform/tenants/{tenantId}/install-tokens

List this tenant's install tokens (state derived: unused / used / expired / revoked).

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/install-tokens" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The tenant's install tokens.
json
[
  {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "state": "unused",
    "expiresAt": "2026-03-02T09:00:00.000Z",
    "usedAt": "2026-03-02T09:00:00.000Z",
    "usedFromIp": "string",
    "revokedAt": "2026-03-02T09:00:00.000Z",
    "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
    "createdByAccountId": "34bcf5dc-34bc-74bc-8bcf-34bcf5dc34bc",
    "label": "string",
    "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.

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/platform/tenants/{tenantId}/install-tokens

Generate a one-time install command for this tenant. The raw token is returned exactly once. Owner only.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

Request body

NameTypeConstraints
label
string1–200 chars

Example

json
{
  "label": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/install-tokens" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "label": "string"
  }'

Responses

201The new token and its ready-to-paste install command.
NameTypeConstraints
tokenrequired
object—
rawTokenrequired
string—
commandrequired
string—
json
{
  "token": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "state": "unused",
    "expiresAt": "2026-03-02T09:00:00.000Z",
    "usedAt": "2026-03-02T09:00:00.000Z",
    "usedFromIp": "string",
    "revokedAt": "2026-03-02T09:00:00.000Z",
    "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
    "createdByAccountId": "34bcf5dc-34bc-74bc-8bcf-34bcf5dc34bc",
    "label": "string",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  },
  "rawToken": "string",
  "command": "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.

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/platform/tenants/{tenantId}/install-tokens/{tokenId}

Revoke an unused install token. A used or already-revoked token cannot be revoked again.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—
tokenIdrequired
pathstring—

curl example

curl
curl -X DELETE "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/install-tokens/3ab6031f-3ab6-7ab6-8b60-3ab6031f3ab6" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The revoked token.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
staterequired
enum: "unused" | "used" | "expired" | "revoked"—
expiresAtrequired
string (date-time)—
usedAtrequired
string (date-time) | null—
usedFromIprequired
string | null—
revokedAtrequired
string (date-time) | null—
createdByrequired
string (uuid) | null—
createdByAccountIdrequired
string (uuid) | null—
labelrequired
string | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "state": "unused",
  "expiresAt": "2026-03-02T09:00:00.000Z",
  "usedAt": "2026-03-02T09:00:00.000Z",
  "usedFromIp": "string",
  "revokedAt": "2026-03-02T09:00:00.000Z",
  "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
  "createdByAccountId": "34bcf5dc-34bc-74bc-8bcf-34bcf5dc34bc",
  "label": "string",
  "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.

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/platform/tenants/{tenantId}/licenses

List every license issued to this tenant.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/licenses" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The tenant’s licenses.
json
[
  {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "jti": "string",
    "issuedAt": "2026-03-02T09:00:00.000Z",
    "expiresAt": "2026-03-02T09:00:00.000Z",
    "lastValidatedAt": "2026-03-02T09:00:00.000Z",
    "status": "valid",
    "metadata": {},
    "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.

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/platform/tenants/{tenantId}/licenses

Issue a signed license for this tenant. The key is returned exactly once. Owner only; requires a step-up MFA check within the last 10 minutes.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

Request body

NameTypeConstraints
ttlDays
integer0–3650 · default: 365

Example

json
{
  "ttlDays": 365
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/licenses" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "ttlDays": 365
  }'

Responses

201The issued license (key returned once) and its record.
NameTypeConstraints
licenserequired
object—
keyrequired
string—
json
{
  "license": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "jti": "string",
    "issuedAt": "2026-03-02T09:00:00.000Z",
    "expiresAt": "2026-03-02T09:00:00.000Z",
    "lastValidatedAt": "2026-03-02T09:00:00.000Z",
    "status": "valid",
    "metadata": {},
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  },
  "key": "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.

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/platform/tenants/{tenantId}/licenses/{licenseId}/revoke

Revoke a license (key compromise / re-issue). Its heartbeats then answer 404, identically to an unknown license.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—
licenseIdrequired
pathstring—

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/licenses/f5f084e9-f5f0-75f0-8f08-f5f084e9f5f0/revoke" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The revoked license.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
jtirequired
string—
issuedAtrequired
string (date-time)—
expiresAtrequired
string (date-time)—
lastValidatedAtrequired
string (date-time) | null—
statusrequired
enum: "valid" | "expired" | "locked" | "revoked"—
metadatarequired
object | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "jti": "string",
  "issuedAt": "2026-03-02T09:00:00.000Z",
  "expiresAt": "2026-03-02T09:00:00.000Z",
  "lastValidatedAt": "2026-03-02T09:00:00.000Z",
  "status": "valid",
  "metadata": {},
  "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.

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/platform/tenants/{tenantId}/registry-credentials

List this tenant's registry credentials (usernames, timestamps — never a secret).

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

curl example

curl
curl -X GET "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/registry-credentials" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The tenant's registry credentials.
json
[
  {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "username": "string",
    "createdByUserId": "d630cadc-d630-7630-830c-d630cadcd630",
    "lastUsedAt": "2026-03-02T09:00:00.000Z",
    "revokedAt": "2026-03-02T09:00:00.000Z",
    "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.

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/platform/tenants/{tenantId}/registry-credentials

Create a registry credential for this tenant. The secret is returned exactly once. Owner only.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/registry-credentials" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

201The new credential and its one-time secret.
NameTypeConstraints
credentialrequired
object—
secretrequired
string—
json
{
  "credential": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "username": "string",
    "createdByUserId": "d630cadc-d630-7630-830c-d630cadcd630",
    "lastUsedAt": "2026-03-02T09:00:00.000Z",
    "revokedAt": "2026-03-02T09:00:00.000Z",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  },
  "secret": "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.

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/platform/tenants/{tenantId}/registry-credentials/{credentialId}/revoke

Revoke a registry credential. Its next token request answers 401.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—
credentialIdrequired
pathstring—

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/registry-credentials/609aae45-609a-709a-89aa-609aae45609a/revoke" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The revoked credential.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
usernamerequired
string—
createdByUserIdrequired
string (uuid) | null—
lastUsedAtrequired
string (date-time) | null—
revokedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "username": "string",
  "createdByUserId": "d630cadc-d630-7630-830c-d630cadcd630",
  "lastUsedAt": "2026-03-02T09:00:00.000Z",
  "revokedAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/tenants/{tenantId}/subscription

Set a tenant’s plan and/or billing period.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

Request body

NameTypeConstraints
planId
string (uuid)—
currentPeriodStart
string (date-time)—
currentPeriodEnd
string (date-time)—

Example

json
{
  "planId": "744bf63f-744b-744b-84bf-744bf63f744b",
  "currentPeriodStart": "2026-03-02T09:00:00.000Z",
  "currentPeriodEnd": "2026-03-02T09:00:00.000Z"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/subscription" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "planId": "744bf63f-744b-744b-84bf-744bf63f744b",
    "currentPeriodStart": "2026-03-02T09:00:00.000Z",
    "currentPeriodEnd": "2026-03-02T09:00:00.000Z"
  }'

Responses

200The updated subscription.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
planIdrequired
string (uuid)—
statusrequired
enum: "trialing" | "active" | "expired" | "suspended" | "cancelled"—
currentPeriodStartrequired
string (date-time)—
currentPeriodEndrequired
string (date-time)—
cancelledAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "planId": "744bf63f-744b-744b-84bf-744bf63f744b",
  "status": "trialing",
  "currentPeriodStart": "2026-03-02T09:00:00.000Z",
  "currentPeriodEnd": "2026-03-02T09:00:00.000Z",
  "cancelledAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/tenants/{tenantId}/subscription/cancel

Cancel a tenant’s subscription.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/subscription/cancel" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The cancelled subscription.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
planIdrequired
string (uuid)—
statusrequired
enum: "trialing" | "active" | "expired" | "suspended" | "cancelled"—
currentPeriodStartrequired
string (date-time)—
currentPeriodEndrequired
string (date-time)—
cancelledAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "planId": "744bf63f-744b-744b-84bf-744bf63f744b",
  "status": "trialing",
  "currentPeriodStart": "2026-03-02T09:00:00.000Z",
  "currentPeriodEnd": "2026-03-02T09:00:00.000Z",
  "cancelledAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/tenants/{tenantId}/subscription/renew

Extend the billing period and set status back to active.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
tenantIdrequired
pathstring—

Request body

NameTypeConstraints
currentPeriodStart
string (date-time)—
currentPeriodEndrequired
string (date-time)—

Example

json
{
  "currentPeriodStart": "2026-03-02T09:00:00.000Z",
  "currentPeriodEnd": "2026-03-02T09:00:00.000Z"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/platform/tenants/4332af8b-4332-7332-832a-4332af8b4332/subscription/renew" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "currentPeriodStart": "2026-03-02T09:00:00.000Z",
    "currentPeriodEnd": "2026-03-02T09:00:00.000Z"
  }'

Responses

200The renewed subscription.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
planIdrequired
string (uuid)—
statusrequired
enum: "trialing" | "active" | "expired" | "suspended" | "cancelled"—
currentPeriodStartrequired
string (date-time)—
currentPeriodEndrequired
string (date-time)—
cancelledAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "planId": "744bf63f-744b-744b-84bf-744bf63f744b",
  "status": "trialing",
  "currentPeriodStart": "2026-03-02T09:00:00.000Z",
  "currentPeriodEnd": "2026-03-02T09:00:00.000Z",
  "cancelledAt": "2026-03-02T09:00:00.000Z",
  "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.

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/platform/users

Search users globally (cursor-paginated).

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
cursor
querystring—
limit
queryinteger1–100 · default: 25
search
querystring1–200 chars

curl example

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

Responses

200A page of users.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "email": "ada.lovelace@example.com",
      "emailVerifiedAt": "2026-03-02T09:00:00.000Z",
      "fullName": "Ada Lovelace",
      "phone": "+15551234567",
      "locale": "ar",
      "avatarFileId": "3561389f-3561-7561-8613-3561389f3561",
      "status": "active",
      "lastLoginAt": "2026-03-02T09:00:00.000Z",
      "failedLoginCount": -9007199254740991,
      "lockedUntil": "2026-03-02T09:00:00.000Z",
      "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.

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/platform/users/{id}/disable

Disable a user account and revoke every one of their sessions.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The user was disabled.
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.

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/platform/users/{id}/enable

Re-enable a disabled user account.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The user was enabled.
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.

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.