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.

37 endpoints

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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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. Returns a signed envelope carrying server time, state, and any pending lock/unlock commands.

No authenticationScopePublic

Request body

NameTypeConstraints
installIdrequired
string (uuid)
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

Example

json
{
  "installId": "2472867d-2472-7472-8728-2472867d2472",
  "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"
  }
}

curl example

curl
curl -X POST "https://api.yourdomain.com/api/v1/platform/licenses/heartbeat" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "installId": "2472867d-2472-7472-8728-2472867d2472",
    "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"
    }
  }'

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://api.yourdomain.com/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/plans

List every plan.

Requires authenticationScopeNo additional permission

curl example

curl
curl -X GET "https://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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
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
  }
}
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://api.yourdomain.com/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"
        }
      },
      "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
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"
    }
  },
  "minFromVersion": "string",
  "notes": null,
  "releasedAt": "2026-03-02T09:00:00.000Z"
}

curl example

curl
curl -X POST "https://api.yourdomain.com/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"
      }
    },
    "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
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"
    }
  },
  "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}/promote

Promote a beta release to the stable channel. Platform owner only — this is what reaches every unattended install.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
releaseIdrequired
pathstring

curl example

curl
curl -X POST "https://api.yourdomain.com/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
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"
    }
  },
  "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.

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://api.yourdomain.com/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 authenticationScopeNo additional permission

Request body

NameTypeConstraints
autoLockEnabled
boolean
autoLockAfterDays
integer0–365

Example

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

curl example

curl
curl -X PATCH "https://api.yourdomain.com/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.

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"

curl example

curl
curl -X GET "https://api.yourdomain.com/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",
      "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://api.yourdomain.com/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)
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",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  },
  "domain": "string",
  "invitationId": "00637243-0063-7063-8637-006372430063"
}
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://api.yourdomain.com/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
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",
    "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
  }
}
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.

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://api.yourdomain.com/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"
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
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",
  "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}/archive

Archive a tenant (data is never deleted).

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring

curl example

curl
curl -X POST "https://api.yourdomain.com/api/v1/platform/tenants/37386ae0-3738-7738-8386-37386ae03738/archive" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

200The tenant, now archived.
NameTypeConstraints
idrequired
string (uuid)
slugrequired
string
namerequired
string
statusrequired
enum: "active" | "suspended" | "archived"
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
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",
  "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).

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://api.yourdomain.com/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}/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://api.yourdomain.com/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.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring

curl example

curl
curl -X POST "https://api.yourdomain.com/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"
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
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",
  "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).

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring

curl example

curl
curl -X POST "https://api.yourdomain.com/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"
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
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",
  "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}/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://api.yourdomain.com/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.

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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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}/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://api.yourdomain.com/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.

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://api.yourdomain.com/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://api.yourdomain.com/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.

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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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://api.yourdomain.com/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.