Skip to content

Commands for platform operations

The commands of the internal szenera-ops tool that run only with an operator profile: device software, approvals, incidents, queues, organizations, operator accounts.

19 minute readCLIUpdated: 10/25/2026

On this page

Before you start

For
Anyone who operates the platform: releases device software, handles incidents, and manages queues and organizations
Prerequisite
the platform’s source tree, a profile with audience: "operator", and the platform.* permissions named in the “Route and permission” column. The “Key” column says whether an operator key opk_… may carry the permission.

The commands of the szenera-ops tool that run only with a profile of the audience operator: an operator account or an operator key opk_…. They do not concern customers of the platform and are not part of the szenera command line. They are listed here because whoami, device, audit, player, playback, permission, api-key, and invoice serve both sides, depending on the profile.

The tool lives in the source tree under tools/ops-cli and is not shipped. It runs with pnpm -s szenera-ops <befehl> and carries all 230 commands of the platform. Profiles, environment variables, output, and exit codes are the same as for szenera. In addition, it knows the audience operator (profile field audience, environment variable SZENERA_AUDIENCE), its own profile file .secrets/szenera-profile.json in the source tree, and the fallback to the environments in .secrets/umgebungen.json. A command that serves only one side rejects the profile of the other before a call goes out.

The tables have the same form as under Commands for your organization: command, purpose, route and permission, whether a key may carry the permission, and whether the command changes something. Operator keys are valid for 1 to 365 days (default 90). Which permissions such a key may carry at all is shown by szenera-ops permission catalog --key-eligible under an operator profile. They are the read-only operations routes, the device software, and reading ad sales. Everything else stays reserved for people with two-factor sign-in. Of the 230 commands of the tool, 136 are listed under Commands for your organization, and they are the commands of szenera. The remaining 94 run only under an operator profile and are listed here. 21 of the 136 serve both sides: 14 of them take a route of their own under an operator profile (whoami, device list, device get, audit list, audit get, player get-program, playback timeline, permission catalog, the four api-key … commands, invoice list, and invoice get), and seven run unchanged (health, health public, and the five commands of the escape hatch api …).

Health

Command Purpose Route and permission Key Modifies
health deep A verdict from five sources, the command to run after a deployment. One red source makes the command red, a silent one appears as unknown GET /health/ready, GET /v1/platform/ops/platform-health, GET …/ops/jobs/health, GET …/ops/cache/health, GET …/ops/events/dispatcher/health, GET …/ops/audit/health · platform.ops.health.read, platform.jobs.health.read, platform.cache.stats.read, platform.events.dispatcher.read, platform.audit.health.read partly: cache and audit log only with an account no

With --json, the response carries the verdict under verdict (green or yellow) and, per source, the fields name, status, and hint under sources. A red verdict ends with exit 11 instead. The counter-check from outside is health public: the same information the status page shows, without a credential and without an operator profile. It answers “What do our customers see right now?” and not “How are things really?”. The command is listed under Commands for your organization.

Fleet devices

Command Purpose Route and permission Key Modifies
device list The devices of all organizations, including those without an organization. Plus --tenant=<tnt_…>, --all-tenants, --silent-only (silent for over a quarter of an hour) GET /v1/platform/ops/devices · platform.ops.device.read yes no
device get <dev_…> One device in the operations view: organization and serial number GET /v1/platform/ops/devices/{deviceId} · platform.ops.device.read yes no
device lookup --serial=<text> Find a device by its hardware serial number GET /v1/platform/ops/devices/lookup · platform.ops.device.read yes no
device get-command <dev_…> <cmd_…> What became of a command sent to a device. --wait waits until the end (exit 11 on failed/timeout, exit 12 on the time limit) GET /v1/platform/ops/devices/{id}/commands/{commandId} · platform.ops.device.command-read yes no
device release <dev_…> --reason=reconnect|factory|removed --yes Release a device. The reason is mandatory. reconnect and removed leave Wi-Fi and remote access in place. factory takes both along. All displays of the device go with it POST /v1/platform/ops/devices/{deviceId}/release · platform.ops.device.reset no yes
device block <dev_…> --reason=<text> --yes Block a device: it loses its organization, all displays go with it, every certificate is revoked, and the API rejects every sign-in with 409 Device.Blocked. The reason (1 to 500 characters) is mandatory POST /v1/platform/ops/devices/{deviceId}/block · platform.ops.device.reset no yes
device unblock <dev_…> Lift the block. The device is then unpaired and can be paired again POST /v1/platform/ops/devices/{deviceId}/unblock · platform.ops.device.reset no yes
device check-ota <dev_…> Make a device look for a new OS version right away. Response 202, --wait additionally needs platform.ops.device.command-read POST /v1/platform/ops/devices/{id}/ota-check · platform.ops.device.ota-check yes yes
device channel --channel=beta|stable|canary --device=<dev_…> --yes Change the channel of several devices in one call, --device may be repeated up to 100 times. Each device gets its own result, and a device that fails does not hold up the others POST /v1/platform/ops/devices/channel · platform.ops.device.channel no yes
player get-program <ply_…> What is assigned to a display, across all organizations, logged GET /v1/platform/ops/devices/{playerId}/program · platform.ops.device.playback-read yes no
playback timeline <ply_…> The resolved playback timeline of a display, across all organizations, logged GET /v1/platform/ops/devices/{playerId}/timeline · platform.ops.device.playback-read yes no

Every call of device get-command appears in the audit log of the organization concerned. An operator profile cannot use device send-command or device screenshot: these routes require fleet.player.command.issue and reject an operator identity. The tool says so before the call.

Incidents and maintenance windows

Command Purpose Route and permission Key Modifies
incident list The incident reports, with --id=<inc_…> a single one including its history GET /v1/platform/ops/incidents · platform.ops.incident.list, with --id GET …/incidents/{id} · platform.ops.incident.read no no
incident create --title=… --severity=sev-1…sev-4 --scope=platform|tenant|device-fleet Open an incident. --public also puts it on the status page, and without the flag it stays internal POST /v1/platform/ops/incidents · platform.ops.incident.open no yes
incident update <inc_…> --message=… Add an entry to the history. --status advances the status at the same time, --public puts the incident on the status page, --internal takes it down PATCH /v1/platform/ops/incidents/{id} · platform.ops.incident.update no yes
incident resolve <inc_…> Close an incident, plus --postmortem=<url> POST /v1/platform/ops/incidents/{id}/resolve · platform.ops.incident.resolve no yes
maintenance-window list The planned maintenance windows, with --id=<mtw_…> a single one GET /v1/platform/ops/maintenance-windows · platform.ops.maintenance.list, with --id platform.ops.maintenance.read no no
maintenance-window create --title=… --start=… --end=… --scope=… Plan a maintenance window. --banner, --notify-days=<tage>, and --block-mutations, which halts the organizations’ write calls POST /v1/platform/ops/maintenance-windows · platform.ops.maintenance.create no yes
maintenance-window update <mtw_…> Move or rewrite a window PATCH /v1/platform/ops/maintenance-windows/{id} · platform.ops.maintenance.update no yes
maintenance-window cancel <mtw_…> --reason=… Cancel a window. The reason goes into the notification DELETE /v1/platform/ops/maintenance-windows/{id} · platform.ops.maintenance.cancel no yes

Queues, cache, events, and connections

Command Purpose Route and permission Key Modifies
job queues The queues with metrics and state, from three sources in one table GET …/ops/jobs/queues, GET …/ops/jobs/metrics, GET …/ops/jobs/health · platform.jobs.queues.read, platform.jobs.metrics.read, platform.jobs.health.read yes no
job pause <name> Pause a queue. What is running finishes POST /v1/platform/ops/jobs/queues/{queueName}/pause · platform.jobs.queues.pause no yes
job resume <name> Resume a paused queue POST /v1/platform/ops/jobs/queues/{queueName}/resume · platform.jobs.queues.resume no yes
job failed The jobs that failed for good, with --not-replayed, --queue=<name>, --id=<kennung> GET /v1/platform/ops/jobs/dead-letter, GET …/dead-letter/{id} · platform.jobs.dead-letter.read yes no
job replay --id=<kennung>|--all --reason=… Enqueue a failed job, or many, again. --max (default 100) caps the number POST …/jobs/dead-letter/{id}/replay, POST …/jobs/dead-letter/bulk-replay · platform.jobs.replay no yes
job kill <jobId> --queue=<name> --reason=… --yes Abort a running job, response 202 POST /v1/platform/ops/jobs/{queueName}/{jobId}/force-kill · platform.jobs.force-kill no yes
job cleanup --yes Clear out old dead-letter entries POST /v1/platform/ops/jobs/cleanup/run · platform.jobs.cleanup no yes
cache stats State, hit rate, and the hot topics, plus --window=1m|5m|1h GET …/ops/cache/health, GET …/ops/cache/stats, GET …/ops/cache/topics · platform.cache.stats.read no no
cache invalidate --topic=<thema>|--pattern=<glob> --reason=… --yes Discard a topic or a pattern POST …/ops/cache/invalidate, POST …/ops/cache/invalidate-pattern · platform.cache.invalidate no yes
event pending What has not been delivered yet. --metrics appends the numbers and health of the dispatcher GET /v1/platform/ops/events/outbox/pending · platform.events.outbox.read yes no
event failed What was not delivered for good GET /v1/platform/ops/events/outbox/dead-letter · platform.events.outbox.read yes no
event get <evt_…> An event with its payload: exactly what was sent GET /v1/platform/ops/events/outbox/{eventId} · platform.events.outbox.read yes no
event replay --id=<evt_…>|--all --reason=… Deliver an event, or many, again, plus --event-type, --since, --max POST …/events/outbox/{eventId}/replay, POST …/events/outbox/replay-bulk · platform.events.outbox.replay no yes
connection list The open real-time connections: display, portal session, or renderer GET /v1/platform/realtime/connections · platform.realtime.read no no
connection disconnect <id> --yes Disconnect a connection. The client then reconnects POST /v1/platform/realtime/connections/{id}/disconnect · platform.realtime.disconnect no yes

The reason for job replay, job kill, cache invalidate, and event replay is mandatory and at least ten characters long.

Error and permission catalog

Command Purpose Route and permission Key Modifies
error catalog All error codes of the API with HTTP status, plus --search=<text> GET /v1/platform/ops/errors/catalog · platform.errors.catalog.read no no
permission catalog The permission catalog. --key-eligible shows what an operator key may carry GET /v1/permission-catalog, sign-in only n/a no
permission drift What the inventory lacks compared with the catalog. Changes nothing GET /v1/platform/ops/permissions/catalog-drift · platform.permissions.catalog.read no no
permission reload Reload the catalog, which is needed when app permissions are added POST /v1/platform/ops/permissions/catalog/reload · platform.permissions.catalog.reload no yes
permission backfill Bring the roles of the organizations up to date with the catalog, plus --tenant=<tnt_…>. --dry-run only calculates, --reconcile also removes. Operator accounts do not need this: their permissions follow the role POST …/ops/permissions/roles/backfill · platform.permissions.role.backfill no yes
permission flush-cache --yes Discard the cache of effective permissions POST /v1/platform/ops/permissions/cache/flush · platform.permissions.cache.flush no yes

Organizations

Command Purpose Route and permission Key Modifies
tenant list The organizations of the platform (active, frozen, suspended, deleted) GET /v1/platform/tenants · platform.tenant.list no no
tenant get <tnt_…> An organization in the operations view GET /v1/platform/tenants/{id} · platform.tenant.read no no
tenant provision --slug=… --name=… --owner-email=… --address=… Organization, contract, owner, and invitation in one go, plus --licenses, --set=<k=v> POST /v1/platform/tenants/provision · platform.tenant.provision, with --set PATCH …/tenants/{tenantId}/feature-entitlements · platform.billing.override no yes
tenant overview <tnt_…> The operations record: fleet, content, contacts GET /v1/platform/tenants/{tenantId}/overview · platform.tenant.read no no
tenant suspend <tnt_…> --reason=… --yes Suspend an organization, which cascades to its accounts POST /v1/platform/tenants/{id}/suspend · platform.tenant.suspend no yes
tenant resume <tnt_…> Lift a suspension POST /v1/platform/tenants/{id}/resume · platform.tenant.resume no yes
tenant delete <tnt_…> --slug=… --yes Delete an organization. This cannot be undone. The short name is the confirmation. Its devices are released and then show a pairing code DELETE /v1/platform/tenants/{id} · platform.tenant.delete no yes
tenant entitlements <tnt_…> Read contractual capabilities and set them with --set=<k=v> (switch on or off) GET …/tenants/{tenantId}/feature-entitlements · platform.billing.invoice.read, PATCH … · platform.billing.override no yes
tenant licensing <tnt_…> Contract status: licenses, items, special terms GET /v1/platform/tenants/{tenantId}/licensing · platform.billing.invoice.read no no
tenant price-override <tnt_…> --sku=<code> Set a special term (--price or --discount, never both) or withdraw it with --clear PUT …/tenants/{tenantId}/licensing/items/{skuCode}/override · platform.billing.override no yes
tenant sso-recovery <tnt_…> --email=… --reason=… --yes Issue SSO emergency access for an account POST /v1/platform/tenants/{tenantId}/sso-recovery · platform.tenant.sso-recovery no yes
self-registration set Read self-registration, switch it with --on/--off --reason=… GET/PUT /v1/platform/self-registration · platform.self-registration.manage no yes

The invitation address from tenant provision appears in plain text in the output (--json: inviteUrl). It is the only place where this token leaves the API.

Operator accounts and operator keys

Command Purpose Route and permission Key Modifies
operator list The operator accounts, with --role=owner|manager|support|viewer, --status, --search, and --with-permission=<platform.…> (accounts whose role carries the permission) GET /v1/platform/operators · platform.operator.list no no
operator get <ops_…> An operator account with its role and the level of the role per bundle GET /v1/platform/operators/{id} · platform.operator.list no no
operator create --email=… --name=… --role=manager|support|viewer Create an operator account. The role is required, owner is not available here. The two-factor setup appears exactly once POST /v1/platform/operators · platform.operator.create no yes
operator set-role <ops_…> --role=manager|support|viewer --yes Replace the role. Only the owner, never the own role, never the owner’s. The new permissions apply with the next request of the account PATCH /v1/platform/operators/{id}/role · platform.operator.create no yes
operator transfer-owner <ops_…> --totp-code=<123456> Transfer the owner role, step 1: the owner makes the account an offer. The offer is valid for seven days, and a new one replaces the earlier one. --accept --totp-code=… (step 2, under the profile of the target) accepts, after which the target is owner and the former owner is manager. --status shows the open offer POST /v1/platform/operators/{id}/owner-transfer, POST …/operators/owner-transfer/accept, GET …/operators/owner-transfer · platform.operator.create, on acceptance and status platform.operator.list no yes
operator suspend <ops_…> --reason=… Suspend an operator account POST /v1/platform/operators/{id}/suspend · platform.operator.suspend no yes
operator reactivate <ops_…> Lift the suspension POST /v1/platform/operators/{id}/reactivate · platform.operator.suspend no yes
operator reset-mfa <ops_…> --reason=… --yes Set up two-factor again. The previous authenticator no longer applies POST /v1/platform/operators/{id}/mfa-reset · platform.operator.mfa-reset no yes
operator resend-onboarding <ops_…> Send the setup email again. It is valid for 72 hours POST /v1/platform/operators/{id}/onboarding-resend · platform.operator.mfa-reset no yes
api-key list The operator keys of the company GET /v1/platform/api-keys · platform.operator.api-key.list no no
api-key issue --name=… --permissions=<a,b,c> Issue an operator key opk_…. --days=<1–365> (default 90), --list-allowed shows the permissions that are allowed POST /v1/platform/api-keys · platform.operator.api-key.create no yes
api-key rotate <oak_…> Renew an operator key, plus --grace-hours=<0–72>, --days POST /v1/platform/api-keys/{id}/rotate · platform.operator.api-key.create no yes
api-key revoke <oak_…> --yes Revoke an operator key, immediately and permanently DELETE /v1/platform/api-keys/{id} · platform.operator.api-key.revoke no yes

Device software

The component is always the first word after the command: os, agent, renderer, host-webos, or host-tizen. A version is named by the command with the build identifier (twelve hex characters), and list shows it.

Command Purpose Route and permission Key Modifies
device-software list <component> The versions in storage, newest first: display name with world (0.6.1-prod), build, state, the channels in which it applies, the number of devices, and the size GET /v1/platform/ops/device-software/{component}/builds · platform.ops.device-software.read yes no
device-software releases <component> What applies in the channels, and the history of releases, with --limit and --cursor GET …/device-software/{component}/releases · platform.ops.device-software.read yes no
device-software devices <component> The devices that run the component, with channel, version, build, and state relative to the release (current, updating, rolled-back, outdated, no-release). Plus --channel, --build, --state GET …/device-software/{component}/devices · platform.ops.device-software.read yes no
device-software activity The history across all components: releases, imports, deletions, requests, and the rollbacks that devices reported. Plus --component=<component> GET …/device-software/activity · platform.ops.device-software.read yes no
device-software release <component> <build> --channel=… --yes Make a version apply in a channel. --pull-along=beta,canary pulls lower channels along in the same step, --note, --security. If the platform requires a second person (releaseApproval.deviceSoftware), a request is created for stable, and --reason=… is then required POST …/device-software/{component}/releases · platform.ops.device-software.release no yes
device-software promote <component> --from=… --to=… --yes Release the build that applies today in the lower channel in the higher one, otherwise like release GET and POST …/device-software/{component}/releases · platform.ops.device-software.release no yes
device-software delete <component> <build> --yes Delete the bytes of a version, the record stays as a tombstone. A version that applies in a channel or that a request points to is protected (DeviceSoftware.BuildInUse, exit 6) DELETE …/device-software/{component}/builds/{build} · platform.ops.device-software.delete no yes
device-software cleanup <component> --dry-run|--yes Delete the versions beyond the newest --keep-newest=<n> (without a value retention.deviceSoftware.keepNewest) that can be deleted. --dry-run only names what would be deleted POST …/device-software/{component}/builds/cleanup · platform.ops.device-software.delete no yes
device-software nudge <component> <dsr_…> Remind the devices of the channel immediately of the applying release POST …/device-software/{component}/releases/{id}/nudge · platform.ops.device-software.release no yes
device-software image <build> The short-lived download address of the SD card image for a SzeneraOS version GET …/device-software/os/builds/{build}/image · platform.ops.device-software.read yes no

The tool does not fetch a build: the environment syncs with the registry every ten minutes, and CI speeds it up with POST …/builds/import-from-registry (platform.ops.device-software.import, with an operator key). release and promote require --yes because a release reaches the devices of the channel.

Requests to a second person

The same for device software and apps. Whether a release to stable needs a second person is set by the owner with settings set releaseApproval.deviceSoftware|apps stable.

Command Purpose Route and permission Key Modifies
approval list The requests, without --state the open ones. Plus --state=open|approved|withdrawn|expired, --kind=device-software|app, --package=<component or app> GET /v1/platform/ops/approvals · platform.ops.device-software.read or platform.app-catalog.list yes no
approval approve <rap_…> Approve a request as the second person and thereby execute it. The person who made the request gets a one-line message and exit 4 (ReleaseApproval.SamePerson) POST /v1/platform/ops/approvals/{id}/approve · platform.ops.release.approve no yes
approval withdraw <rap_…> Withdraw a request, or reject it as another person. --reason=… is optional POST /v1/platform/ops/approvals/{id}/withdraw · platform.ops.release.approve or platform.ops.device-software.release or platform.app-catalog.promote no yes

Platform settings

Command Purpose Route and permission Key Modifies
settings get All platform settings in full, one that is not set with its default GET /v1/platform/settings · platform.settings.read no no
settings set <setting> <value> --yes Change a setting, for example releaseApproval.apps stable or retention.deviceSoftware.keepNewest 3. The value is checked against the contract before anything goes out PATCH /v1/platform/settings · platform.settings.manage (owner only) no yes

The settings are releaseApproval.deviceSoftware and releaseApproval.apps (off or stable, default off) as well as retention.deviceSoftware.keepNewest (1 to 100, default 5) and retention.deviceSoftware.auto (default false). In addition there are retention.apps.keepNewest (1 to 100, default 5), retention.apps.unreferencedMonths (1 to 120, default 12), and retention.apps.auto (default false). Every change appears in the audit log with the old and the new value (platform.settings.changed).

App catalog

Command Purpose Route and permission Key Modifies
app-release list The releases of the app catalog, with --app=<app-id>, --channel=canary|beta|stable|deprecated|retired GET /v1/platform/app-catalog/apps, GET …/apps/{appId}/releases · platform.app-catalog.list no no
app-release set-notes <arl_…> --app=<app-id> --notes=… Change the release notes (up to 2000 characters) PATCH …/apps/{appId}/releases/{id} · platform.app-catalog.update no yes
app-release promote <arl_…> --app=<app-id> --channel=beta|stable Promote to beta or stable, always one step at a time. To stable, the command promotes immediately if the platform does not require a second person (releaseApproval.apps = off), otherwise it creates a request, and --reason=… (10 to 500 characters) is then mandatory POST …/apps/{appId}/releases/{id}/promote · platform.app-catalog.promote no yes
app-release restore <arl_…> --app=<app-id> --reason=… Roll back an outdated or superseded version to stable. Immediately or, depending on the setting, as a request that a second person confirms with approval approve. The version that applied until then goes to deprecated POST …/apps/{appId}/releases/{id}/restore · platform.app-catalog.promote no yes
app-release demote <arl_…> --app=<app-id> --channel=deprecated Demote a release from stable to deprecated, the only step backward POST …/apps/{appId}/releases/{id}/demote · platform.app-catalog.promote no yes
app-release retire <arl_…> --app=<app-id> --reason=… --grace=regular|security|immediate Take a release out of service. --grace chooses the retirement period and has no default: regular three months, security one month, in which case the security advisory --advisory=… is mandatory for the organizations, and immediate no period, so the platform switches at once to the next higher version in stable. For regular and immediate there is no advisory POST …/apps/{appId}/releases/{id}/retire · platform.app-catalog.retire no yes
app-release grace-period <arl_…> --app=<app-id> Extend the retirement period, by --months=<n> from its current end or to the end of a day with --until=<jjjj-mm-tt>, or end it at once with --immediately --reason=…. Exactly one choice POST …/releases/{id}/extend-grace, POST …/releases/{id}/force-retire · platform.app-catalog.retire no yes
app-release delete-artifact <arl_…> --app=<app-id> --yes Delete the bytes of a version, the row, manifest, and history remain. A version in a channel, with an installation, a scene, or an open request is protected (AppRelease.ArtifactInUse, exit 6) DELETE …/apps/{appId}/releases/{id}/artifact · platform.app-catalog.delete no yes
app-release cleanup --app=<app-id> --dry-run|--yes Delete the bytes of all versions beyond the newest --keep-newest=<n> (without a value retention.apps.keepNewest) that can be deleted. --dry-run only names what would be deleted POST …/apps/{appId}/releases/cleanup · platform.app-catalog.delete no yes
app-status list The apps with their operator status, installations, and exceptions GET /v1/platform/app-catalog/managed-apps · platform.app-catalog.list no no
app-status set <app> --status=active|beta|disabled --reason=… Set an app platform-wide PATCH /v1/platform/app-catalog/managed-apps/{appId} · platform.app-catalog.status.manage no yes
app-status override <app> Read exceptions per organization, set them with --tenant and --status, remove them with --clear GET …/managed-apps/{appId}/overrides · platform.app-catalog.list, PUT …/overrides/{tenantId}, POST …/overrides/{tenantId}/remove · platform.app-catalog.status.manage no yes

Whether a single person promotes to stable is decided by the setting releaseApproval.apps. If the platform requires a second person and POST …/promote with stable names no reason, it answers AppRelease.SecondPersonRequired (exit 6). With a reason, a request is created that a second person confirms with approval approve, and it expires after seven days. If the checklist of the version carries a block, promote (to beta as well as to stable) and the confirmation of a request to stable refuse with AppRelease.ReviewBlocked (exit 6), and the command names the blocked items: “New in this version” is missing in the required language, or a customer text carries a semicolon or a blocked word. “New in this version” is created in the review of the version in Szenera Ops, and set-notes sets only the text for operations.

Billing and items

Command Purpose Route and permission Key Modifies
subscription set-payment-way <tnt_…> --way=invoice|stripe|none Set the payment way of a contract PUT /v1/platform/tenants/{tenantId}/billing/payment-way · platform.billing.override no yes
subscription approve-invoice <tnt_…> --yes Approve the purchase on invoice. --revoke withdraws the approval PUT /v1/platform/tenants/{tenantId}/billing/payment-way · platform.billing.override no yes
subscription check-stripe <tnt_…> Check the link of a contract to the payment provider. Changes nothing GET /v1/platform/tenants/{tenantId}/billing/stripe-link · platform.billing.override no no
subscription relink-stripe <tnt_…> --yes Link a contract to the payment provider again. If the customer still exists there, nothing happens (unchanged) POST /v1/platform/tenants/{tenantId}/billing/relink-stripe · platform.billing.override no yes
subscription backfill-period-prices --yes Catch up the per-period unit prices of the existing contracts: the fix when the run at application startup failed. Idempotent POST /v1/platform/billing/period-unit-prices/backfill · platform.billing.plan.manage no yes
invoice list --tenant=<tnt_…>|--all-tenants The invoices of all organizations GET /v1/platform/invoices · platform.billing.invoice.read no no
invoice get <inv_…> An invoice with its line items GET /v1/platform/invoices/{id} · platform.billing.invoice.read no no
invoice mark-paid <inv_…> --yes Book a payment received POST /v1/platform/invoices/{id}/mark-paid · platform.billing.invoice.mark-paid no yes
invoice refund <inv_…> --reason=… --yes Refund an invoice POST /v1/platform/invoices/{id}/refund · platform.billing.invoice.refund no yes
invoice issue-documents <inv_…> --yes Generate the PDF and e-invoice of a document after the fact. A document that already carries its files stays untouched POST /v1/platform/invoices/{id}/issue-documents · platform.billing.invoice.refund no yes
catalog list The item catalog with the special terms that are set GET /v1/platform/billing/catalog · platform.billing.plan.manage, GET /v1/platform/billing/overrides · platform.billing.override no no
catalog set [sku] Replace a catalog item (all five fields) or set the monthly price of an app with --app=<app-id> --price=<cent> PUT /v1/platform/billing/catalog/{skuCode}, PUT /v1/platform/billing/app-prices/{appId} · platform.billing.plan.manage no yes

Under an operator profile, audit list and audit get also run against GET /v1/platform/audit and GET /v1/platform/audit/{id} (platform.audit.read). --tenant or --all-tenants is mandatory there.