portal

Customer Portal

An Institflow account that owns a tenant managing its licence view, install command, registry credentials and relay statement — read-only except for minting the same install token/registry credential a console operator could. Cloud/control-plane only: answers 404 on a self-hosted install.

8 endpoints

GET /api/v1/portal/institute

The owned institute: licence state/expiry, install state, subscription plan, and the relay balance. Read-only.

Requires authenticationScopeNo additional permission

curl example

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

Responses

200The institute.
NameTypeConstraints
tenantrequired
object—
licenserequired
object | null—
addressrequired
string | null—
installrequired
object | null—
subscriptionrequired
object | null—
relayrequired
object—
json
{
  "tenant": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "slug": "string",
    "name": "Algebra I",
    "status": "active"
  },
  "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"
  },
  "address": "string",
  "install": {
    "installId": "2472867d-2472-7472-8728-2472867d2472",
    "appVersion": "string",
    "state": "provisioning",
    "lastSeenAt": "2026-03-02T09:00:00.000Z",
    "updateChannel": "stable",
    "updaterLastResult": "up_to_date",
    "updaterLastReason": "none",
    "needsAdoption": true,
    "adoptionCommands": [
      "value"
    ]
  },
  "subscription": {
    "planCode": "string",
    "planName": "string",
    "status": "trialing",
    "currentPeriodEnd": "2026-03-02T09:00:00.000Z"
  },
  "relay": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "balanceUnits": -9007199254740991,
    "overdraftUnits": -9007199254740991,
    "status": "active",
    "ratePerMinute": -9007199254740991,
    "ratePerDay": -9007199254740991,
    "reputationHold": true,
    "reputationHoldReason": "string",
    "senderDisplayName": "string",
    "senderNameApprovedAt": "2026-03-02T09:00:00.000Z",
    "senderNameRequestedAt": "2026-03-02T09:00:00.000Z",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z",
    "spend30dUnits": -9007199254740991,
    "installCount": -9007199254740991,
    "pendingInstallCount": -9007199254740991
  }
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

POST /api/v1/portal/institute/install-command

Generate a one-time install command for the owned institute. The raw token is returned exactly once.

Requires authenticationScopeNo additional permission

Request body

NameTypeConstraints
label
string1–200 chars

Example

json
{
  "label": "string"
}

curl example

curl
curl -X POST "https://your-institute.example/api/v1/portal/institute/install-command" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "label": "string"
  }'

Responses

201The new token and its ready-to-paste install command.
NameTypeConstraints
tokenrequired
object—
rawTokenrequired
string—
commandrequired
string—
json
{
  "token": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "state": "unused",
    "expiresAt": "2026-03-02T09:00:00.000Z",
    "usedAt": "2026-03-02T09:00:00.000Z",
    "usedFromIp": "string",
    "revokedAt": "2026-03-02T09:00:00.000Z",
    "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
    "createdByAccountId": "34bcf5dc-34bc-74bc-8bcf-34bcf5dc34bc",
    "label": "string",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  },
  "rawToken": "string",
  "command": "string"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

GET /api/v1/portal/institute/install-tokens

The owned institute’s install tokens (state derived: unused / used / expired / revoked).

Requires authenticationScopeNo additional permission

curl example

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

Responses

200The install tokens.
json
[
  {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "state": "unused",
    "expiresAt": "2026-03-02T09:00:00.000Z",
    "usedAt": "2026-03-02T09:00:00.000Z",
    "usedFromIp": "string",
    "revokedAt": "2026-03-02T09:00:00.000Z",
    "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
    "createdByAccountId": "34bcf5dc-34bc-74bc-8bcf-34bcf5dc34bc",
    "label": "string",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  }
]
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

DELETE /api/v1/portal/institute/install-tokens/{id}

Revoke an unused install token.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The revoked token.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
staterequired
enum: "unused" | "used" | "expired" | "revoked"—
expiresAtrequired
string (date-time)—
usedAtrequired
string (date-time) | null—
usedFromIprequired
string | null—
revokedAtrequired
string (date-time) | null—
createdByrequired
string (uuid) | null—
createdByAccountIdrequired
string (uuid) | null—
labelrequired
string | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "state": "unused",
  "expiresAt": "2026-03-02T09:00:00.000Z",
  "usedAt": "2026-03-02T09:00:00.000Z",
  "usedFromIp": "string",
  "revokedAt": "2026-03-02T09:00:00.000Z",
  "createdBy": "0b14e5a6-0b14-7b14-814e-0b14e5a60b14",
  "createdByAccountId": "34bcf5dc-34bc-74bc-8bcf-34bcf5dc34bc",
  "label": "string",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

POST /api/v1/portal/institute/registry-credential

Re-issue the owned institute’s registry credential (shown once). Revokes any still-active one first.

Requires authenticationScopeNo additional permission

curl example

curl
curl -X POST "https://your-institute.example/api/v1/portal/institute/registry-credential" \
  -H "Authorization: Bearer $INSTITFLOW_API_KEY"

Responses

201The new credential and its one-time secret.
NameTypeConstraints
credentialrequired
object—
secretrequired
string—
json
{
  "credential": {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "username": "string",
    "createdByUserId": "d630cadc-d630-7630-830c-d630cadcd630",
    "lastUsedAt": "2026-03-02T09:00:00.000Z",
    "revokedAt": "2026-03-02T09:00:00.000Z",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  },
  "secret": "string"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

GET /api/v1/portal/institute/registry-credentials

The owned institute’s registry credentials (usernames, timestamps — never a secret).

Requires authenticationScopeNo additional permission

curl example

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

Responses

200The registry credentials.
json
[
  {
    "id": "37386ae0-3738-7738-8386-37386ae03738",
    "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
    "username": "string",
    "createdByUserId": "d630cadc-d630-7630-830c-d630cadcd630",
    "lastUsedAt": "2026-03-02T09:00:00.000Z",
    "revokedAt": "2026-03-02T09:00:00.000Z",
    "createdAt": "2026-03-02T09:00:00.000Z",
    "updatedAt": "2026-03-02T09:00:00.000Z"
  }
]
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

DELETE /api/v1/portal/institute/registry-credentials/{id}

Revoke a registry credential.

Requires authenticationScopeNo additional permission

Path & query parameters

NameInTypeConstraints
idrequired
pathstring—

curl example

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

Responses

200The revoked credential.
NameTypeConstraints
idrequired
string (uuid)—
tenantIdrequired
string (uuid)—
usernamerequired
string—
createdByUserIdrequired
string (uuid) | null—
lastUsedAtrequired
string (date-time) | null—
revokedAtrequired
string (date-time) | null—
createdAtrequired
string (date-time)—
updatedAtrequired
string (date-time)—
json
{
  "id": "37386ae0-3738-7738-8386-37386ae03738",
  "tenantId": "4332af8b-4332-7332-832a-4332af8b4332",
  "username": "string",
  "createdByUserId": "d630cadc-d630-7630-830c-d630cadcd630",
  "lastUsedAt": "2026-03-02T09:00:00.000Z",
  "revokedAt": "2026-03-02T09:00:00.000Z",
  "createdAt": "2026-03-02T09:00:00.000Z",
  "updatedAt": "2026-03-02T09:00:00.000Z"
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

GET /api/v1/portal/institute/relay/statement

The owned institute’s relay ledger, newest first. Read-only.

Requires authenticationScopeNo additional permission

Path & query parameters

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

curl example

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

Responses

200A page of ledger entries.
NameTypeConstraints
datarequired
array<object>—
nextCursorrequired
string | null—
json
{
  "data": [
    {
      "id": "37386ae0-3738-7738-8386-37386ae03738",
      "accountId": "fec33a71-fec3-7ec3-8c33-fec33a71fec3",
      "kind": "topup",
      "amountUnits": -9007199254740991,
      "balanceAfterUnits": -9007199254740991,
      "messageId": "1ac1c849-1ac1-7ac1-8c1c-1ac1c8491ac1",
      "priceId": "48d77787-48d7-78d7-8d77-48d7778748d7",
      "reason": "Requested by guardian.",
      "note": "string",
      "actorUserId": "7d892c08-7d89-7d89-8892-7d892c087d89",
      "createdAt": "2026-03-02T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
400The request could not be read at all — unparseable body, unusable content type, oversized payload (`MALFORMED_REQUEST`). Never a schema failure: those are 422.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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