# Instance API

8 operations under /api/v1. Errors are RFC 9457 problem+json with a stable `code` field. The complete schemas are in https://nebulactrl.com/api/v1/openapi.json.

## GET /instance/backup

Get the control-plane backup settings

A scheduled pg_dump of NebulaCtrl's own database to one instance-wide destination. The instance secret key is never part of a backup. Instance admin only.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Response 200: OK (InstanceBackupView)
  - `destination` (InstanceDestinationView, required)
  - `enabled` (boolean, required)
  - `keyAcknowledgedAt` (string (date-time)): When an admin confirmed the instance secret key is stored separately. The reminder card shows until then.
  - `keyFingerprint` (string, required)
  - `lastRun` (InstanceRunView)
  - `lastSuccessAt` (string (date-time))
  - `localDirEnabled` (boolean, required): False when NEBULACTRL_BACKUP_DIR is not set: local directory destinations are unavailable.
  - `nextRunAt` (string (date-time))
  - `retentionCount` (integer (int64), required): Successful backups kept (the newest is never removed).
  - `schedule` (string, required): 5-field cron expression, evaluated in UTC.
  - `toolsAvailable` (boolean, required): False when pg_dump is missing on the control-plane host: backups can not run.
  - `toolsMessage` (string)
  - `updatedAt` (string (date-time), required)
Errors: 401 Unauthorized; 403 Forbidden; 500 Internal Server Error

## PATCH /instance/backup

Update the control-plane backup settings

Credentials are write-only. Enabling needs a complete destination. Instance admin only.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Request body (application/json, required): PatchInstanceBackupInputBody
  - `accessKey` (string): Write-only. Omit or leave empty to keep the stored key.
  - `bucket` (string)
  - `compatMode` (boolean)
  - `enabled` (boolean): Run the schedule. Needs a complete destination.
  - `endpoint` (string): S3-compatible endpoint URL; empty means AWS S3.
  - `kind` (enum: s3 | local): Changing the kind starts the destination afresh.
  - `localDir` (string): Local destinations: relative sub-directory of NEBULACTRL_BACKUP_DIR.
  - `pathStyle` (boolean)
  - `prefix` (string): Key prefix inside the bucket or directory.
  - `region` (string)
  - `retentionCount` (integer (int64)): Successful backups kept (default 14).
  - `schedule` (string): 5-field cron expression or @daily etc., evaluated in UTC (default 30 3 * * *). At most one run every 5 minutes.
  - `secretKey` (string): Write-only. Omit or leave empty to keep the stored key.

Response 200: OK (InstanceBackupView)
  - `destination` (InstanceDestinationView, required)
  - `enabled` (boolean, required)
  - `keyAcknowledgedAt` (string (date-time)): When an admin confirmed the instance secret key is stored separately. The reminder card shows until then.
  - `keyFingerprint` (string, required)
  - `lastRun` (InstanceRunView)
  - `lastSuccessAt` (string (date-time))
  - `localDirEnabled` (boolean, required): False when NEBULACTRL_BACKUP_DIR is not set: local directory destinations are unavailable.
  - `nextRunAt` (string (date-time))
  - `retentionCount` (integer (int64), required): Successful backups kept (the newest is never removed).
  - `schedule` (string, required): 5-field cron expression, evaluated in UTC.
  - `toolsAvailable` (boolean, required): False when pg_dump is missing on the control-plane host: backups can not run.
  - `toolsMessage` (string)
  - `updatedAt` (string (date-time), required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## POST /instance/backup/key-acknowledgement

Confirm the instance secret key is stored separately

Dismisses the reminder card. Backups do not contain NEBULACTRL_SECRET_KEY, and a restore is useless without it: store it in a password manager or vault, apart from the backups.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Response 200: OK (InstanceBackupView)
  - `destination` (InstanceDestinationView, required)
  - `enabled` (boolean, required)
  - `keyAcknowledgedAt` (string (date-time)): When an admin confirmed the instance secret key is stored separately. The reminder card shows until then.
  - `keyFingerprint` (string, required)
  - `lastRun` (InstanceRunView)
  - `lastSuccessAt` (string (date-time))
  - `localDirEnabled` (boolean, required): False when NEBULACTRL_BACKUP_DIR is not set: local directory destinations are unavailable.
  - `nextRunAt` (string (date-time))
  - `retentionCount` (integer (int64), required): Successful backups kept (the newest is never removed).
  - `schedule` (string, required): 5-field cron expression, evaluated in UTC.
  - `toolsAvailable` (boolean, required): False when pg_dump is missing on the control-plane host: backups can not run.
  - `toolsMessage` (string)
  - `updatedAt` (string (date-time), required)
Errors: 401 Unauthorized; 403 Forbidden; 500 Internal Server Error

## GET /instance/backup/runs

List control-plane backup runs

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `limit` (query, integer (int64))

Response 200: OK (InstanceRunListOutputBody)
  - `items` (InstanceRunView[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## POST /instance/backup/runs

Back up the control plane now

Returns 202 with the running backup; poll the runs or listen for `instance.backup.updated` events. 409 `backup_in_progress` while one is running.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `Idempotency-Key` (header, string): Client-chosen key (<=255 chars). A retry with the same key replays the original response for 24 hours.

Response 202: Accepted (InstanceRunView)
  - `actorLabel` (string)
  - `destKind` (enum: s3 | local, required)
  - `destination` (string, required): Where the backup was written.
  - `error` (string)
  - `finishedAt` (string (date-time))
  - `id` (string, required)
  - `objectDeleted` (boolean, required): Retention removed the stored objects; the row stays as history.
  - `objectKey` (string)
  - `pgVersion` (string)
  - `sha256` (string)
  - `sizeBytes` (integer (int64), required)
  - `startedAt` (string (date-time), required)
  - `status` (enum: running | succeeded | failed, required)
  - `trigger` (enum: schedule | manual | pre_upgrade, required)
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict (`backup_in_progress`); 422 Unprocessable Entity; 500 Internal Server Error

## POST /instance/backup/test

Test the control-plane backup destination

The control plane writes, reads back and deletes a probe object. The verdict is stored on the settings.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Response 200: OK (InstanceBackupView)
  - `destination` (InstanceDestinationView, required)
  - `enabled` (boolean, required)
  - `keyAcknowledgedAt` (string (date-time)): When an admin confirmed the instance secret key is stored separately. The reminder card shows until then.
  - `keyFingerprint` (string, required)
  - `lastRun` (InstanceRunView)
  - `lastSuccessAt` (string (date-time))
  - `localDirEnabled` (boolean, required): False when NEBULACTRL_BACKUP_DIR is not set: local directory destinations are unavailable.
  - `nextRunAt` (string (date-time))
  - `retentionCount` (integer (int64), required): Successful backups kept (the newest is never removed).
  - `schedule` (string, required): 5-field cron expression, evaluated in UTC.
  - `toolsAvailable` (boolean, required): False when pg_dump is missing on the control-plane host: backups can not run.
  - `toolsMessage` (string)
  - `updatedAt` (string (date-time), required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## GET /instance/dashboard-access

Get the dashboard access settings

Who may reach the dashboard's hostname (an IP allow list on its system route) and how its certificate is issued (automatic HTTP-01 or a DNS-01 resolver of the control-plane server). `clientIp` is the caller's address as the allow list sees it. Instance admin only.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Response 200: OK (DashboardAccessView)
  - `allowCidrs` (string[] | null, required)
  - `certificate` (DashboardCertificate, required)
  - `clientIp` (string, required): The caller's address as the dashboard route's allow list sees it. Empty when it cannot be determined
  - `clientIpAllowed` (boolean, required): The current allow list admits clientIp (always true while it is empty or the caller does not come through the route)
  - `dnsResolver` (string, required): DNS-01 provider of the certificate; empty is automatic HTTP-01
  - `enabled` (boolean, required): The dashboard has a system route (an https public URL with a DNS name, installer layout). false: nothing to restrict
  - `host` (string, required): The dashboard's hostname
  - `proxyDeployRequired` (boolean, required)
  - `proxyServerId` (string, required): The control-plane server, where Deploy proxy lives
  - `resolverAvailable` (DashboardResolverOption[] | null, required): Providers with a resolver on the control-plane server (Servers, Proxy)
  - `source` (enum: default | installer | admin, required): Where the stored settings come from. installer: seeded from the installer's environment and still overwritten by it on restart; admin: saved here, the installer's environment is ignored
  - `updatedAt` (string (date-time) | null, required)
  - `viaDashboardRoute` (boolean, required): The caller reached the control plane through the dashboard route, so the allow list applies to them
Errors: 401 Unauthorized; 403 Forbidden; 500 Internal Server Error

## PUT /instance/dashboard-access

Update the dashboard access settings

Replaces the allow list and the certificate method and re-sends the control-plane server's routes: an allow list applies immediately. Agents on other servers and webhooks reach the control plane through the same hostname, so keep their addresses in the list. 409 `dashboard_lockout` when a non-empty list excludes the caller's current address, unless `confirmLockout` is true. 422 `resolver_not_configured` when the control-plane server has no resolver of that provider. A resolver that is staged but not applied yet sets `proxyDeployRequired`: the dashboard keeps its HTTP-01 certificate until Deploy proxy. 409 `dashboard_route_disabled` when the dashboard has no system route. Instance admin only.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Request body (application/json, required): UpdateDashboardAccessInputBody
  - `allowCidrs` (string[] | null, required): CIDRs or single addresses (IPv4 or IPv6) allowed to reach the dashboard. Empty lifts the restriction.
  - `confirmLockout` (boolean): Save even though allowCidrs excludes the caller's current address (otherwise 409 dashboard_lockout).
  - `dnsResolver` (string): DNS-01 provider of the dashboard's certificate (a resolver the control-plane server already has, see resolverAvailable). Empty is automatic HTTP-01.

Response 200: OK (DashboardAccessView)
  - `allowCidrs` (string[] | null, required)
  - `certificate` (DashboardCertificate, required)
  - `clientIp` (string, required): The caller's address as the dashboard route's allow list sees it. Empty when it cannot be determined
  - `clientIpAllowed` (boolean, required): The current allow list admits clientIp (always true while it is empty or the caller does not come through the route)
  - `dnsResolver` (string, required): DNS-01 provider of the certificate; empty is automatic HTTP-01
  - `enabled` (boolean, required): The dashboard has a system route (an https public URL with a DNS name, installer layout). false: nothing to restrict
  - `host` (string, required): The dashboard's hostname
  - `proxyDeployRequired` (boolean, required)
  - `proxyServerId` (string, required): The control-plane server, where Deploy proxy lives
  - `resolverAvailable` (DashboardResolverOption[] | null, required): Providers with a resolver on the control-plane server (Servers, Proxy)
  - `source` (enum: default | installer | admin, required): Where the stored settings come from. installer: seeded from the installer's environment and still overwritten by it on restart; admin: saved here, the installer's environment is ignored
  - `updatedAt` (string (date-time) | null, required)
  - `viaDashboardRoute` (boolean, required): The caller reached the control plane through the dashboard route, so the allow list applies to them
Errors: 401 Unauthorized; 403 Forbidden; 409 Conflict (`dashboard_lockout`, `dashboard_route_disabled`); 422 Unprocessable Entity (`resolver_not_configured`); 500 Internal Server Error
