{"openapi":"3.1.0","info":{"title":"canopy public-server","description":"Internet-facing API for the canopy fleet. Device-authenticated endpoints require an mTLS client certificate.\n\nRequest bodies may be sent compressed, and this is recommended for large payloads: the server transparently decodes `Content-Encoding: gzip`, `br`, `deflate`, or `zstd`. The accepted encodings are also listed structurally in the `x-request-compression` extension.","contact":{"name":"BES Developers","email":"contact@bes.au"},"license":{"name":"GPL-3.0-or-later"},"version":""},"paths":{"/artifacts/{version}/{artifact_type}/{platform}":{"post":{"tags":["artifacts"],"summary":"Register a downloadable artifact for a version or version range.","description":"Requires a device certificate with the releaser role (or admin). The\npath identifies the version the artifact belongs to — either an exact\nversion (e.g. `2.10.5`) or a semver range pattern (e.g. `2.10.x`,\n`^2.10.0`) — followed by the artifact's type and target platform. The\nrequest body is the plain-text URL clients should download the\nartifact from.\n\nWhen an exact version is given and it doesn't exist yet, it is created\nautomatically as an unpublished draft so the artifact has a version to\nattach to; publishing that version later (via the version-creation\nendpoint) is a separate step. When a range pattern is given instead,\nthe artifact isn't tied to one version — it matches whichever\npublished version currently satisfies the range at lookup time.\n\nReturns the created artifact record. Returns 400 if the version or\nrange syntax can't be parsed.","operationId":"register_artifact","parameters":[{"name":"version","in":"path","description":"Exact semver (e.g. `2.10.5`) or range pattern (e.g. `2.10.x`, `^2.10.0`).","required":true,"schema":{"type":"string"}},{"name":"artifact_type","in":"path","required":true,"schema":{"type":"string"}},{"name":"platform","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Download URL for the artifact, as a plain-text body.","content":{"text/plain":{"schema":{"type":"string"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Artifact"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"releaser-device":[]}]}},"/backup-capabilities":{"post":{"tags":["backup"],"summary":"Register the backup types this server can run.","description":"Declares the set of backup types the calling device is able to execute on\nits server. Types not seen before are added to the server's capability set,\nstarting out enabled or disabled according to the fleet-wide default for\nthat type (disabled if the type has no default configured). Types already\nregistered keep whatever enabled/disabled state an operator has set for\nthem, so re-registering on every startup is safe and expected.\n\nOnly types that are registered here (and enabled) can later be issued\ncredentials via `POST /backup-credentials`, outside of explicit\noperator-requested runs.\n\nErrors: 412 when the calling device is not bound to a live server; 409 when\nthe server is not in a group.","operationId":"register_backup_capabilities","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupCapabilitiesArgs"}}},"required":true},"responses":{"204":{"description":"Capabilities registered."},"409":{"description":"Server is not in a group.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"412":{"description":"Device is not bound to a live server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"server-device":[]}]}},"/backup-credentials":{"post":{"tags":["backup"],"summary":"Mint short-lived S3 credentials for a backup or restore run.","description":"Issues temporary AWS credentials scoped to the backup storage of the\ncalling server's group, in the `credential_process` output format. With\n`purpose: backup` the credentials can upload and manage backup data but\ncannot destroy existing backups; with `purpose: restore` they are strictly\nread-only. They expire after at most one hour, so request a fresh set for\neach run rather than caching them. Every issuance is recorded for audit.\n\nThe storage coordinates the credentials apply to (bucket, prefix, region)\ncome from `GET /backup-target`.\n\nErrors: 409 when the server is not in a group, when the group's backup\nconfiguration is not ready, or when the requested type is neither an\nenabled capability of this server nor the subject of a pending\noperator-requested run of the given purpose; 412 when the device is not\nbound to a live server; 502 when the credential issuer is unavailable or\nnot configured.","operationId":"mint_backup_credentials","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupCredentialsArgs"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialProcessOutput"}}}},"409":{"description":"Server ungrouped, no ready config, or type not enabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"412":{"description":"Device is not bound to a live server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"502":{"description":"STS issuance failed or is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"server-device":[]}]}},"/backup-report":{"post":{"tags":["backup"],"summary":"Report the outcome of a backup or restore run.","description":"Records the run against the calling server and its group. Send one report\nper run, on success and on failure alike. Reporting also clears any pending\noperator-requested run for the same type and purpose — regardless of\noutcome, since an operator request is for one attempt — so the server's\nstatus responses stop asking for it (see the `backup_now` field of the\nstatus-push response).\n\nErrors: 409 when the server is not in a group, or when the `run_id` has\nalready been reported; 412 when the device is not bound to a live server.","operationId":"report","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportArgs"}}},"required":true},"responses":{"204":{"description":"Run recorded."},"409":{"description":"Server ungrouped, or duplicate run_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"412":{"description":"Device is not bound to a live server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"server-device":[]}]}},"/backup-target":{"get":{"tags":["backup"],"summary":"Fetch the backup storage target for this server's group.","description":"Returns the bucket, prefix, region, and repository passphrase the device\nneeds to connect to its group's backup repository. Call it on every run\nrather than caching the result, as the target can change. S3 credentials\nare obtained separately via `POST /backup-credentials`.\n\nErrors: 409 when the server is not in a group or the group's backup\nconfiguration is not ready; 412 when the device is not bound to a live\nserver; 502 when the passphrase store is unavailable or not configured.","operationId":"target","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupTarget"}}}},"409":{"description":"Server ungrouped or no ready config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"412":{"description":"Device is not bound to a live server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"502":{"description":"Repo-password Secret unavailable or kube not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"server-device":[]}]}},"/bestool/snippets":{"get":{"tags":["bestool"],"summary":"List all current bestool SQL snippets.","description":"Returns the library of named SQL snippets that devices running bestool\nfetch and run, keyed by snippet name. Only the current version of each\nsnippet is included: if a snippet has been superseded by a newer one\nunder the same name, only the newer version is returned, and\nsoft-deleted snippets are omitted entirely. This endpoint does not\nrequire device authentication.","operationId":"list_snippets","responses":{"200":{"description":"Current bestool snippets, keyed by name.","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/SnippetResponse"},"propertyNames":{"type":"string"}}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}}}},"/events":{"post":{"tags":["events"],"summary":"Report an event against the calling device's server.","description":"Requires a device certificate with the server role (or admin). Used to\npush a status update — a healthcheck result, an alert condition, or a\n\"condition cleared\" notice — which canopy records as an issue and, if\nthe server belongs to a group, may fold into an incident. An event\nwith the same `source` and `ref` as an already-open issue on this\nserver updates that issue instead of opening a new one.\n\nThe calling device must already be enrolled against exactly one\nserver, otherwise the request fails with 412. The `source` value\n`\"manual\"` is reserved for operator-entered events and is rejected\nwith 400 here, as is an empty `ref`.\n\nReturns the issue the event was recorded against (existing or newly\ncreated).","operationId":"submit_event","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewEvent"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Issue"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"412":{"description":"Device is not registered against any server.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"server-device":[]}]}},"/restore-capabilities":{"post":{"tags":["restore"],"summary":"Register the restore intents this device can satisfy.","description":"Declares the restore intents the calling device supports, replacing any\npreviously advertised set. Only worklist entries whose intent is currently\nadvertised are dispatched to this device via `GET /restore-worklist`, so\nregister on startup and whenever the supported set changes.","operationId":"register_restore_capabilities","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestoreCapabilitiesArgs"}}},"required":true},"responses":{"204":{"description":"Capability set registered."}},"security":[{"backup-restore-device":[]}]}},"/restore-credentials":{"post":{"tags":["restore"],"summary":"Mint read-only credentials for a group's backup repository.","description":"Issues temporary AWS credentials — always strictly read-only, scoped to the\ngroup's backup storage — together with the repository passphrase, so the\ndevice can read the snapshot named in a worklist entry. Credentials expire\nafter at most one hour; request a fresh set per restore rather than caching\nthem. Every issuance is recorded for audit.\n\nThe device must hold an enabled restore declaration covering the requested\ngroup and type (i.e. the pair must appear in its worklist configuration);\notherwise the request is rejected with 403.\n\nErrors: 403 when no enabled declaration authorizes this group and type;\n409 when the group has no ready backup configuration; 502 when the\ncredential issuer or the passphrase store is unavailable or not configured.","operationId":"mint_restore_credentials","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestoreCredentialsArgs"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RestoreCredentials"}}}},"403":{"description":"No enabled declaration authorizes this (group, type).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"409":{"description":"Group has no ready backup config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"502":{"description":"STS issuance or repo-password read failed or is not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"backup-restore-device":[]}]}},"/restore-verification":{"post":{"tags":["restore"],"summary":"Report the outcome of a restore attempt and the replica's health.","description":"Records a verification report for a restore the device performed from its\nworklist. Send one report per attempt, on success and on failure alike. A\nreport with a `success` outcome and `replica_healthy: true` marks the\nsnapshot as verified; for run-once intents this is what removes the entry\nfrom `GET /restore-worklist` until a newer snapshot appears.\n\nAuthorization matches `POST /restore-credentials`: the device must hold an\nenabled restore declaration covering the reported group and type,\notherwise the request is rejected with 403.","operationId":"verification","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationArgs"}}},"required":true},"responses":{"204":{"description":"Report recorded."},"403":{"description":"No enabled declaration authorizes this (group, type).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"backup-restore-device":[]}]}},"/restore-worklist":{"get":{"tags":["restore"],"summary":"Fetch the full set of replicas this device should maintain.","description":"Returns the device's complete desired state, computed fresh on every call:\neach enabled restore declaration whose intent this device currently\nadvertises, expanded into one entry per server it covers. A group-wide\ndeclaration expands to every live server in its group; a server-scoped\ndeclaration yields a single entry and takes precedence over a group-wide\none covering the same server, type, and intent. Entries for groups whose\nbackup configuration is not ready are omitted, and entries for run-once\nintents disappear once the latest snapshot has a healthy verification\nreport, reappearing when a newer snapshot exists.\n\nAn empty array means there is nothing to do. Poll this endpoint and\nreconcile: create or refresh the replicas listed, and tear down any the\ndevice is maintaining that no longer appear.","operationId":"worklist","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WorklistEntry"}}}}}},"security":[{"backup-restore-device":[]}]}},"/servers":{"get":{"tags":["servers"],"summary":"List publicly-listed central servers.","description":"Returns every central server that has both a public display name and a\nreachable host configured, ordered by environment tier (production\nfirst, then clone, demo, test, dev) and then by name. Used by clients\nto let a user pick which server to connect to.","operationId":"list_servers","responses":{"200":{"description":"Publicly-listed central servers, ordered by rank then name.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicServer"}}}}},"500":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}}}},"/servers/register/begin":{"post":{"tags":["servers"],"summary":"Start device enrollment against a server.","description":"Validates the enrollment token against the given server and, if valid,\nissues a short-lived (5 minute) signed challenge bound to the server\nID, the token, and the caller's public key. The device must sign this\nchallenge and submit it to the completion endpoint to finish\nenrollment; the token itself is validated here but not yet consumed.\n\nThis endpoint is rate-limited per source IP and per target server; a\ntripped limit returns 429. Any other failure — an unknown or deleted\nserver, or an invalid or expired token — is surfaced as a generic 403,\ndeliberately not distinguishing which check failed.","operationId":"register_begin","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeginArgs"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeginResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}}}},"/servers/register/complete":{"post":{"tags":["servers"],"summary":"Complete device enrollment by presenting a signed challenge.","description":"Verifies the signature over the challenge transcript using the public\nkey supplied here, then binds the device to the server: an existing\ndevice re-enrolling with the same key is reused as-is; a device\nre-enrolling with a different key replaces the server's previous\ndevice (revoking that device's access); otherwise a new device\nidentity is created. On success the device is granted the server\nrole, the enrollment token is consumed, and the server is marked as\nregistered.\n\nEnrollment is refused if the presented public key is already bound to\na different live server. Like the start-enrollment endpoint, this one\nis rate-limited per source IP and per target server (429 on a tripped\nlimit) and reports every other kind of failure as a generic 403.","operationId":"register_complete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteArgs"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteResponse"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}}}},"/status/{server_id}":{"post":{"tags":["statuses"],"summary":"Submit a status heartbeat for a server.","description":"Records a periodic status push against the server identified in the\npath: overall self-reported health, a per-check breakdown, and any\nfree-form extra data. Each failed or warning check opens (or keeps\nopen) an issue at that check's operator-configured severity, and each\npassed check closes any issue it previously opened; the server's\ntracked software version is also updated from the payload.\n\nThe calling device must be the one enrolled for this exact server (or\nhold the admin role). The response echoes back the stored status\nrecord, plus a `backup_now` list of backup types the server should\nback up immediately — devices should treat a non-empty list as a\nprompt to run those backups and report them afterwards.","operationId":"submit_status","parameters":[{"name":"server_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"Status push. A `health` array is required (it may be empty); a body without it is rejected with 400, unless the server has `allow_legacy_status` set, in which case the push refreshes reachability only and carries the last known healthchecks forward.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusPayload"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"server-device":[]}]}},"/tags":{"get":{"tags":["tags"],"summary":"Get the tags for the calling device's own server.","description":"Returns the effective set of tags for the server the calling device is\nregistered as: any tags set on the server itself, overlaid onto any tags\ninherited from its server group (a tag set on the server takes precedence\nover a group tag with the same key). If the server isn't in a group,\nthis returns just its own tags.\n\nThe result also includes a few read-only, synthetic tags describing the\nserver, under the reserved `canopy:` key prefix: `canopy:kind`,\n`canopy:rank` (if the server has one set), and `canopy:group-id` /\n`canopy:group-name` (if the server belongs to a group). Operators cannot\nset tags under that prefix, so these never collide with tags you set\nyourself.\n\n- **401**: the request has no client certificate, or the certificate\n doesn't match a known device.\n- **409**: the calling device is attached to more than one server, which\n should not normally happen; contact support if you see this.\n- **412**: the device is registered but has not yet been attached to a\n server.","operationId":"get_self","responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagMap"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"409":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"412":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"server-device":[]}]}},"/versions":{"get":{"tags":["versions"],"summary":"List published, ready-to-serve versions.","description":"Returns every version currently in the published state, excluding any\nversion a recorded known-issue range still covers (whether that issue\nis still open or has since been fixed in a later patch). Ordered\nnewest first.","operationId":"list_versions","responses":{"200":{"description":"All published versions.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Version"}}}}}}}},"/versions/update-for/{version}":{"get":{"tags":["versions"],"summary":"Check for available updates from a given version.","description":"The path parameter is the caller's currently-installed exact version.\nFor each later minor release line within the same major version,\nreturns the latest published version that hasn't been excluded by a\nrecorded known-issue range — falling back to an older ready patch\nwithin that same minor line rather than dropping the line entirely, if\nthe newest patch isn't ready. Clients use this to discover and offer\navailable updates.","operationId":"update_for","parameters":[{"name":"version","in":"path","description":"Currently-installed version (exact semver).","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Published versions above the given one, used by clients to discover updates.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ViewVersion"}}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}}}},"/versions/{version}":{"post":{"tags":["versions"],"summary":"Publish a version with its changelog.","description":"Requires a device certificate with the releaser role (or admin). The\npath parameter is the exact version being published (e.g. `2.10.5`);\nthe request body is the changelog for that version, as up to 1 MiB of\nmarkdown text.\n\nIf the version already exists in the draft state — for example\nbecause an artifact was registered against it before its changelog\nwas written — the draft is published in place, with this changelog\nreplacing whatever it had before. Otherwise a new version is created\ndirectly in the published state. Publishing a version that already\nexists and is not a draft (already published, or yanked) fails.\n\nReturns the resulting version record.","operationId":"create_version","parameters":[{"name":"version","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Changelog markdown text, up to 1 MiB.","content":{"text/plain":{"schema":{"type":"string"}}},"required":true},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Version"}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"releaser-device":[]}]},"delete":{"tags":["versions"],"summary":"Yank a version.","description":"Requires a device certificate with the admin role. Marks the given\nexact version as yanked, hiding it from listings, update checks, and\nartifact lookups without deleting its history.","operationId":"remove","parameters":[{"name":"version","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Version marked yanked."},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"401":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"403":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}},"security":[{"admin-device":[]}]}},"/versions/{version}/artifacts":{"get":{"tags":["versions"],"summary":"List the artifacts available for a version or version range.","description":"The path parameter accepts either an exact version or a semver range\npattern (e.g. `2.10.x`, `^2.10.0`). It resolves to the latest\npublished, ready version satisfying the input, then returns that\nversion's artifacts — both ones registered against the exact version\nand ones registered against a range pattern that covers it. Returns\n404 if no published, ready version matches.","operationId":"list_artifacts","parameters":[{"name":"version","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Artifacts that match the given exact version or range.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Artifact"}}}}},"400":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}},"404":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetailsSchema"}}}}}}}},"components":{"schemas":{"Artifact":{"type":"object","description":"A downloadable artifact belonging to a release version: an installer,\npackage, or other file published for a given type and platform.","required":["id","artifact_type","platform","download_url"],"properties":{"artifact_type":{"type":"string","description":"What kind of artifact this is (e.g. an installer or package name)."},"device_id":{"type":["string","null"],"format":"uuid","description":"The device that registered this artifact, if it was registered by a\nreleaser device rather than created by an operator."},"download_url":{"type":"string","description":"URL the artifact can be downloaded from."},"id":{"type":"string","format":"uuid","description":"Unique identifier of the artifact."},"platform":{"type":"string","description":"The platform the artifact targets (e.g. an OS or architecture name)."},"version_id":{"type":["string","null"],"format":"uuid","description":"The exact version this artifact belongs to. `null` for range\nartifacts, which apply to every version matching\n`version_range_pattern` instead."},"version_range_pattern":{"type":["string","null"],"description":"Semver range this artifact applies to (e.g. `^2.10.0`), for artifacts\nshared across a range of versions rather than pinned to one. `null`\nfor exact-version artifacts."}}},"BTreeMap":{"type":"object","additionalProperties":{"type":"object","description":"Describes one configurable parameter that a replica of a restore intent\naccepts.","required":["type"],"properties":{"default":{"description":"The value used when the parameter is left unset. `None` means an\nunset parameter is sent as JSON `null` rather than a default value."},"type":{"$ref":"#/components/schemas/ParamType","description":"The parameter's data type, which determines how its value is\nvalidated."}}},"propertyNames":{"type":"string"}},"BackupCapabilitiesArgs":{"type":"object","description":"Request body for registering the backup types a server can run.","required":["types"],"properties":{"types":{"type":"array","items":{"type":"string"},"description":"The backup types this server is able to run. Each type is a plain\nstring (e.g. `tamanu-postgres`); custom type names are accepted."}}},"BackupCredentialsArgs":{"type":"object","description":"Request body for minting short-lived S3 credentials for a backup or\nrestore run.","required":["type"],"properties":{"purpose":{"$ref":"#/components/schemas/BackupPurpose","description":"What the credentials will be used for. `backup` (the default) grants\nwrite access for uploading backups; `restore` grants strictly read-only\naccess. Either way the credentials are scoped to the group's backup\nstorage only."},"type":{"type":"string","description":"The backup type the credentials are for (e.g. `tamanu-postgres`). The\ntype must be an enabled capability of this server, or the subject of a\npending operator-requested run matching `purpose`; otherwise the request\nis rejected with 409."}}},"BackupPurpose":{"type":"string","description":"Why a backup credential was issued, or what a reported run was for.\nDetermines the access the credential grants: a `backup` credential can\nwrite new data but not delete existing data, while a `restore`\ncredential is read-only.","enum":["backup","restore"]},"BackupTarget":{"type":"object","description":"The backup storage target for the calling server's group: where the backup\nrepository lives and the passphrase to open it.","required":["storage","bucket","prefix","region","repo_password"],"properties":{"bucket":{"type":"string","description":"Name of the S3 bucket holding the group's backup repository."},"prefix":{"type":"string","description":"Key prefix within the bucket under which the repository lives. Normally\nempty (the repository is at the bucket root)."},"region":{"type":"string","description":"AWS region of the bucket."},"repo_password":{"type":"string","description":"Passphrase for the group's backup repository (a Kopia repository)."},"storage":{"type":"string","description":"Kind of storage backend. Always `\"s3\"`."}}},"BeginArgs":{"type":"object","description":"Request to start device enrollment against a server.","required":["server_id","token"],"properties":{"server_id":{"type":"string","format":"uuid","description":"ID of the server to enroll against."},"spki":{"type":["string","null"],"description":"Base64-standard-encoded DER SubjectPublicKeyInfo (SPKI) of the\ndevice's public key. Only required when enrolling over a transport\nwith no client certificate to read the key from (e.g. over\nTailscale); omit it when enrolling over mTLS, where the key is\ntaken from the presented client certificate. The returned challenge\nis bound to this key, so the same value must be supplied again when\ncompleting enrollment."},"token":{"type":"string","description":"The enrollment token issued by an operator for this server."}}},"BeginResponse":{"type":"object","description":"A freshly-issued enrollment challenge to sign and return.","required":["nonce","channel_binding_required"],"properties":{"channel_binding_required":{"type":"boolean","description":"True if the server requires channel-binding data (the connection's\nTLS exported keying material) to be folded into the signed\ntranscript. Only relevant when enrolling over mTLS; it never\napplies on a transport without TLS channel binding."},"nonce":{"type":"string","description":"Base64-standard-encoded 32-byte challenge nonce. Sign it, together\nwith the server ID, device public key, and channel-binding data if\nrequired, and submit the signature when completing enrollment."}}},"CheckResult":{"type":"string","description":"Outcome of a single health check reported in a server's status update.\n\nOlder reports may send a plain pass/fail flag instead of one of these\noutcomes; when that happens a passing flag is treated as `passed` and a\nfailing flag as `failed`.","enum":["passed","warning","failed","broken","skipped"]},"CompleteArgs":{"type":"object","description":"Request to complete device enrollment by presenting a signed\nchallenge obtained from the start-enrollment endpoint.","required":["server_id","nonce","signature"],"properties":{"nonce":{"type":"string","description":"Base64-standard-encoded challenge nonce returned when enrollment\nwas started."},"server_id":{"type":"string","format":"uuid","description":"ID of the server being enrolled against. Must match the value used\nwhen starting enrollment."},"signature":{"type":"string","description":"Base64-standard-encoded ASN.1 DER ECDSA (P-256, SHA-256) signature\nover the challenge transcript, proving possession of the device's\nprivate key. The transcript is the byte concatenation of: the raw\nchallenge nonce, the raw 16 bytes of the server ID, the DER SPKI of\nthe device public key, and — only when channel binding was flagged\nas required — the connection's TLS exported keying material."},"spki":{"type":["string","null"],"description":"Base64-standard-encoded DER SPKI of the device's public key. Only\nrequired when enrolling over a transport with no client\ncertificate to read the key from; must match the key used when\nenrollment was started."}}},"CompleteResponse":{"type":"object","description":"Result of a successful enrollment.","required":["server_id","device_id"],"properties":{"device_id":{"type":"string","format":"uuid","description":"The device identity created or reused for this enrollment. The\ndevice authenticates as this ID from now on."},"server_id":{"type":"string","format":"uuid","description":"The server the device is now enrolled against."}}},"CredentialProcessOutput":{"type":"object","description":"Short-lived AWS credentials in the AWS `credential_process` output format,\nso they can be consumed directly by AWS SDKs and tools. Field names use the\nexact casing (`Version`, `AccessKeyId`, ...) that format requires.","required":["Version","AccessKeyId","SecretAccessKey","SessionToken","Expiration"],"properties":{"AccessKeyId":{"type":"string","description":"The temporary AWS access key ID."},"Expiration":{"type":"string","description":"When the credentials expire, as an RFC 3339 / ISO 8601 UTC instant.\nCredentials last at most one hour; request a fresh set per run."},"SecretAccessKey":{"type":"string","description":"The temporary AWS secret access key."},"SessionToken":{"type":"string","description":"The session token that must accompany the temporary key pair."},"Version":{"type":"integer","format":"int32","description":"Version of the `credential_process` format. Always the literal `1`.","minimum":0}}},"HealthCheck":{"allOf":[{"type":"object","description":"Arbitrary additional fields specific to this check (shown in the\nstatus UI as a key/value block, and available to operator-defined\nseverity rules)."},{"type":"object","required":["check"],"properties":{"check":{"type":"string","description":"Name of the check. Must be a non-empty string, and should stay stable\nacross pushes: results for the same name are correlated over time, so\nsuccessive failures and the eventual recovery land on the same issue."},"healthy":{"type":["boolean","null"],"description":"Legacy pass/fail form: `true` means `passed`, `false` means `failed`.\nMutually exclusive with `result`."},"result":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/CheckResult","description":"Outcome of the check: `passed`, `warning`, `failed`, `broken`, or\n`skipped`. Exactly one of `result` / `healthy` must be present per\nentry. `warning` and `failed` open an issue at the check's catalog\nseverity; `broken` (the check itself errored, not the system under\ntest) opens a separate issue at a fixed Warning severity without\nconfirming or clearing a known failure; `skipped` (a precondition was\nnot met) and `passed` open nothing and close prior issues."}]}}}],"description":"One health-check result within a status push."},"IntentDescriptor":{"type":"object","description":"One restore purpose a consumer advertises support for: the behaviours it\nopts into and the settings it accepts per replica.","required":["intent"],"properties":{"description":{"type":["string","null"],"description":"Human-readable description of the intent, if provided."},"intent":{"type":"string","description":"Name of the intent: an arbitrary identifier chosen by the consumer\n(e.g. `verify`); any name may be advertised."},"params":{"$ref":"#/components/schemas/BTreeMap","description":"Configurable parameters this intent accepts per replica, keyed by\nparameter name."},"semantics":{"type":"array","items":{"type":"string"},"description":"Behaviours this intent opts into. Recognised values are `check` (a\nhealth report is expected for each replica), `once` (a given snapshot\nis only ever dispatched to a replica once, rather than repeatedly\nuntil overdue), and `url` (a replica's health report includes a link\nto it). Unrecognised values are stored but have no effect."}}},"Issue":{"type":"object","description":"A tracked problem or condition on a server, or on a server group as a\nwhole. An issue is opened the first time its source reports it and stays\nopen (accumulating events) until the source reports it as no longer\nactive or an operator resolves it. Issues are the basic unit that drives\nincidents, Slack notifications, and the fleet health view.","required":["id","created_at","updated_at","source","ref","severity","message","active","first_seen","last_seen"],"properties":{"active":{"type":"boolean","description":"Whether the condition behind this issue is still ongoing. Set to\n`false` when the source reports the condition has cleared."},"created_at":{"type":"string","format":"date-time","description":"When this issue was first created."},"description":{"type":["string","null"],"description":"A short, single-line title for the issue, shown as its headline in\nthe UI and in Slack notifications. `None` if no title was given."},"device_id":{"type":["string","null"],"format":"uuid","description":"The device that reported this issue, if it was raised by a device\npush. `None` for issues raised by an operator or by the platform\nitself."},"first_seen":{"type":"string","format":"date-time","description":"When this issue was first reported."},"id":{"type":"string","format":"uuid","description":"Unique identifier for this issue."},"last_seen":{"type":"string","format":"date-time","description":"When this issue was most recently reported or updated."},"message":{"type":"string","description":"The full body text describing the issue."},"ref":{"type":"string","description":"A caller-chosen identifier for this issue within its `source`. The\nsame `(source, ref)` pair reported again updates this issue instead\nof creating a new one."},"resolved_at":{"type":["string","null"],"format":"date-time","description":"When an operator marked this issue resolved. `None` if it hasn't\nbeen resolved."},"resolved_by":{"type":["string","null"],"description":"The operator who resolved this issue. `None` if it hasn't been\nresolved."},"resolved_reason":{"type":["string","null"],"description":"The reason given when the issue was resolved (for example: fixed,\nfalse positive, won't fix). `None` if it hasn't been resolved."},"server_group_id":{"type":["string","null"],"format":"uuid","description":"The server group this issue is attached to, for a control-plane issue\n(e.g. backup corruption, a failed preflight check) that isn't tied to\nany single server. `None` for an ordinary server-scoped issue.\nGroup-scoped issues are always considered even if an individual\nserver in the group has monitoring turned off."},"server_id":{"type":["string","null"],"format":"uuid","description":"The server this issue is attached to. `None` for a group-scoped issue\n(see `server_group_id`) — exactly one of the two is always set."},"severity":{"$ref":"#/components/schemas/Severity","description":"The issue's current severity."},"snoozed_until":{"type":["string","null"],"format":"date-time","description":"If set, this issue is snoozed until this time: it's temporarily\nexcluded from incidents and notifications even while still active."},"source":{"type":"string","description":"Identifies what raised this issue — a healthcheck, a backup pipeline,\nan operator, etc. Used together with `ref` to detect repeat reports\nof the same underlying problem."},"updated_at":{"type":"string","format":"date-time","description":"When this issue was last modified."}}},"NewEvent":{"type":"object","description":"A single occurrence to report against an issue, sent either by a device\nor by an operator. If an issue with the same `source` and `ref` is\nalready open, this occurrence is folded into it (bumping its last-seen\ntime, and coalescing into the latest recorded event when the content is\nidentical); otherwise a new issue is created.","required":["source","ref","message"],"properties":{"active":{"type":["boolean","null"],"description":"Whether the condition this event describes is still ongoing.\nDefaults to `true`. Sending `false` marks the condition as cleared,\nwhich can close the issue's contribution to any open incident."},"description":{"type":["string","null"],"description":"A short, single-line title for this event, shown as the issue's\nheadline in the UI and as the subject of any Slack notification.\nMust not contain newlines. Use `message` for the full body text."},"message":{"type":"string","description":"The full body text for this event. Free-form, multi-line text is\nfine. If `description` is omitted, the UI falls back to using the\nfirst line of this field as the headline."},"occurredAt":{"type":["string","null"],"format":"date-time","description":"When the event actually happened, if known and different from when\nit was received. Defaults to the time the event was received."},"ref":{"type":"string","description":"A caller-chosen identifier for this specific problem within `source`,\nused to detect repeated reports of the same underlying condition.\nRequired — mint a UUID if deduplication isn't needed."},"severity":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/Severity","description":"Severity of this event. Defaults to a standard severity if omitted."}]},"source":{"type":"string","description":"Identifies what is reporting this event — a healthcheck, a backup\npipeline, an operator, etc."}}},"ParamType":{"type":"string","description":"The data type of a restore-replica configuration parameter, which\ndetermines how its value is validated. `duration` and `bytes` values must\nbe non-negative integers (a count of seconds and of bytes, respectively);\n`integer` accepts any whole number, positive or negative; `boolean` is a\nJSON boolean; `text` is a JSON string.","enum":["duration","bytes","boolean","integer","text"]},"ProblemDetailsSchema":{"type":"object","description":"Standard error response body, returned for every non-2xx response.\n\nThis follows the RFC 7807 \"Problem Details\" shape: a stable machine-readable\n`type`, a short `title`, the repeated HTTP `status` code, and an optional\n`detail` string with specifics of this particular occurrence.","required":["type","title","status"],"properties":{"detail":{"type":["string","null"],"description":"Human-readable explanation specific to this occurrence of the\nproblem, if any extra detail is available."},"status":{"type":"integer","format":"int32","description":"HTTP status code of the response, repeated here for convenience.","example":404,"minimum":0},"title":{"type":"string","description":"Short, human-readable summary of the problem type. Does not vary\nbetween occurrences of the same `type`."},"type":{"type":"string","format":"uri","description":"A URI reference identifying the problem type. Stable across\noccurrences of the same error, so callers can match on it.","example":"/errors/resource-not-found"}}},"PublicServer":{"type":"object","description":"A publicly-listed central server that a client can connect to.","required":["name","host"],"properties":{"host":{"$ref":"#/components/schemas/UrlField","description":"The server's reachable base URL."},"name":{"type":"string","description":"Public-facing display name of the server."},"rank":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ServerRank","description":"The server's environment tier (production, clone, demo, test, or\ndev), if set. Used to order the listing and to let clients label\nnon-production entries."}]}}},"ReportArgs":{"type":"object","description":"Report of a completed backup or restore run.","required":["run_id","type","purpose","outcome"],"properties":{"bytes_uploaded":{"type":["integer","null"],"format":"int64","description":"Total bytes of backup data uploaded during the run, if known."},"error":{"type":["string","null"],"description":"Human-readable error detail, when the run failed."},"outcome":{"$ref":"#/components/schemas/RunOutcome","description":"Whether the run succeeded (`success`) or failed (`failure`)."},"purpose":{"$ref":"#/components/schemas/BackupPurpose","description":"Whether the run was a `backup` or a `restore`."},"run_id":{"type":"string","format":"uuid","description":"Client-generated UUID identifying this run, minted at run start. Each\nrun must use a fresh UUID: reporting the same `run_id` twice is\nrejected with 409."},"s3_received_payload_bytes":{"type":["integer","null"],"format":"int64","description":"Bytes of decoded object payload received from S3 during the run."},"s3_received_raw_bytes":{"type":["integer","null"],"format":"int64","description":"Bytes of raw HTTP traffic received from S3 during the run, including\nprotocol overhead."},"s3_sent_payload_bytes":{"type":["integer","null"],"format":"int64","description":"Bytes of decoded object payload sent to S3 during the run (excluding\nprotocol and signing overhead)."},"s3_sent_raw_bytes":{"type":["integer","null"],"format":"int64","description":"Bytes of raw HTTP traffic sent to S3 during the run, including protocol\nand signing overhead. Report on both success and failure; omit when\ntraffic was not measured."},"snapshot_id":{"type":["string","null"],"description":"Identifier of the repository snapshot the run produced, for a\nsuccessful backup."},"type":{"type":"string","description":"The backup type that ran (e.g. `tamanu-postgres`)."}}},"RestoreCapabilitiesArgs":{"type":"object","description":"Request body for registering the restore intents a consumer device can\nsatisfy.","required":["intents"],"properties":{"intents":{"type":"array","items":{"$ref":"#/components/schemas/IntentDescriptor"},"description":"The intents this device can satisfy — arbitrary consumer-chosen\nidentifiers (e.g. `verify`) — each with its description, the semantics\nit opts into, and the schema of the parameters it accepts. Replaces the\ndevice's previously advertised set wholesale."}}},"RestoreCredentials":{"type":"object","description":"Read-only S3 credentials plus the repository passphrase for one group and\nbackup type: everything needed to open the group's backup repository and\nread a snapshot out of it.","required":["credentials","repo_password"],"properties":{"credentials":{"$ref":"#/components/schemas/CredentialProcessOutput","description":"Temporary read-only AWS credentials in the `credential_process` output\nformat, valid for at most one hour."},"repo_password":{"type":"string","description":"Passphrase for the group's backup repository (a Kopia repository)."}}},"RestoreCredentialsArgs":{"type":"object","description":"Request body for minting read-only restore credentials.","required":["group","type"],"properties":{"group":{"type":"string","format":"uuid","description":"The server group whose backup repository to read."},"type":{"type":"string","description":"The backup type to restore (e.g. `tamanu-postgres`)."}}},"RunOutcome":{"type":"string","description":"Outcome of a reported backup or restore run.","enum":["success","failure"]},"ServerRank":{"type":"string","description":"The environment tier of a server, from `production` down to `dev`.","enum":["production","clone","demo","test","dev"]},"Severity":{"type":"string","description":"Canopy's severity vocabulary, narrowed from RFC 5424 to a five-level\nset with operator semantics:\n\n- `Debug`: never participates in incidents (filed for the audit\n trail / per-server view only).\n- `Info`, `Warning`: join an open incident for context but don't\n open one or keep one open on their own.\n- `Error`: opens an incident; sits in the per-group `slack_open_delay`\n holding window before the Slack notification ships.\n- `Critical`: opens an incident and bypasses the holding window —\n the Slack notification is enqueued for immediate delivery.\n\nStored as text in Postgres; validated as this enum at the API layer.\nDefault is `Error` (the most common severity for a deliberately\nfiled event). The legacy syslog severities `emergency` / `alert` /\n`notice` have been retired — see the\n`2026-05-29-000000-0000_restrict_severities` migration; the\n`FromStr` impl still accepts them as aliases for forward-compat with\nany device that hasn't been updated.","enum":["critical","error","warning","info","debug"]},"SnippetResponse":{"type":"object","description":"A single named SQL snippet from the bestool snippet library.","required":["sql"],"properties":{"description":{"type":["string","null"],"description":"Human-readable explanation of what the snippet does, if the author\nprovided one."},"sql":{"type":"string","description":"The snippet's SQL text."}}},"Status":{"type":"object","description":"A stored status report from a server: one heartbeat's worth of\nself-reported health, version, and extra data.","required":["id","created_at","server_id","extra","healthy","health"],"properties":{"created_at":{"type":"string","format":"date-time","description":"When this status was received."},"device_id":{"type":["string","null"],"format":"uuid","description":"The device that submitted this status, or `null` for statuses\ngenerated internally (e.g. by the reachability sweep)."},"extra":{"description":"Free-form extra data from the report (uptime, database version,\ntimezone, etc.), stored verbatim as a JSON object."},"health":{"description":"Per-check breakdown, as an array of objects each with at least a\n`check` name and a result; any extra per-check fields are passed\nthrough verbatim."},"healthy":{"type":"boolean","description":"Server's overall self-reported health. A report that omits this is\nrecorded as healthy."},"id":{"type":"string","format":"uuid","description":"Unique identifier of this status record."},"server_id":{"type":"string","format":"uuid","description":"The server this status was reported for."},"version":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/VersionStr","description":"The software version the server reported, if it reported one."}]}}},"StatusPayload":{"allOf":[{"type":"object","description":"Free-form additional data (uptime, database version, timezone,\nhostname, etc.). Stored verbatim and surfaced as raw JSON in the\nstatus view.\n\nA `tamanuVersion` field here is used as the server's tracked version\n(compared against the published version catalog), superseding the\nlegacy `X-Version` request header. If both are present,\n`tamanuVersion` wins; if neither is, the status is recorded without a\nversion."},{"type":"object","required":["health"],"properties":{"health":{"type":"array","items":{"$ref":"#/components/schemas/HealthCheck"},"description":"Per-check breakdown. **Required** — a push without a `health` array is\nrejected with 400 (unless an operator has opted the server into the\nlegacy format, in which case such a push only refreshes reachability\nand carries the previously reported checks forward). May be empty\n(`[]`) for a server that genuinely runs no checks. Each entry must\ninclude a non-empty `check` name and exactly one of `result` /\n`healthy`; any additional fields per check (latency, free disk %,\ncertificate expiry, etc.) are passed through verbatim and shown in the\nstatus UI.\n\nEvery check name seen — whatever its result — is added to the\noperator-facing check catalog, where the severity assigned to its\nfailures can be reviewed and adjusted. A failed or warning check opens\n(or keeps open) an issue for that check at the catalog's current\nseverity; a broken check opens a separate issue at a fixed Warning\nseverity; passed and skipped results open nothing and close prior\nissues."},"healthy":{"type":["boolean","null"],"description":"Overall self-reported health of the server. **Absent means `true`**,\nso senders that predate this field are never treated as unhealthy by\nomission. Recorded for historical analysis and display, but **not\nconsulted for incident or severity decisions** — those are derived\nfrom the per-check results in `health`, with each check's severity\ncontrolled by an operator-managed catalog."}}}],"description":"A status push: a server's periodic heartbeat carrying its self-reported\nhealth.\n\nBesides the reserved `healthy` and `health` keys described here, any\nadditional top-level fields are accepted and stored verbatim as extra\nstatus data."},"StatusResponse":{"allOf":[{"$ref":"#/components/schemas/Status","description":"The status record as stored, flattened into the top level of the\nresponse."},{"type":"object","required":["backup_now"],"properties":{"backup_now":{"type":"array","items":{"type":"string"},"description":"Backup types the server should back up now: operator-requested\none-offs plus scheduled backups that are due. Each serializes as a\nplain string (e.g. `\"tamanu-postgres\"`). The device should run each\nlisted type, then report via `POST /backup-report`; an empty list\nmeans nothing to do."}}}],"description":"The status-push response: the stored status record (its fields appear at\nthe top level of the response object) plus `backup_now`, the backup types\nthis server should back up *right now*. Clients that predate `backup_now`\ncan safely ignore it."},"TagMap":{"type":"object","description":"Free-form key/value tags, as a JSON object whose values are all strings.","additionalProperties":{"type":"string"}},"UrlField":{"type":"string","format":"uri","description":"A URL, given as a plain string. Any trailing slash is stripped when the\nvalue is returned."},"VerificationArgs":{"type":"object","description":"Report of a restore attempt and the health of the resulting replica.","required":["group","server_id","type","intent","outcome","replica_healthy","observed_at"],"properties":{"error":{"type":["string","null"],"description":"Human-readable error detail, when the restore failed."},"group":{"type":"string","format":"uuid","description":"The server group whose backup was restored."},"health_details":{"description":"Arbitrary structured health data to record alongside the report\n(database statistics, whether indexes needed rebuilding, and so on).\nStored and displayed as-is."},"intent":{"type":"string","description":"The restore intent this attempt was performed under."},"observed_at":{"type":"string","description":"When the restore result was observed, as an RFC 3339 timestamp."},"outcome":{"type":"string","description":"Whether the restore succeeded (`success`) or failed (`failure`)."},"postgres_version":{"type":["string","null"],"description":"Version of the PostgreSQL server the data was restored into, if\napplicable."},"replica_healthy":{"type":"boolean","description":"Whether the restored database came up healthy and passed readiness\nchecks. A replica only counts as verified when the outcome is\n`success` and this is `true`."},"replica_id":{"type":["string","null"],"format":"uuid","description":"The declaration this report concerns, taken from the worklist entry's\n`replica_id`. Optional so a report is still accepted when the\ndeclaration was retired while the restore was in flight."},"s3_received_payload_bytes":{"type":["integer","null"],"format":"int64","description":"Bytes of decoded object payload received from S3 during the restore."},"s3_received_raw_bytes":{"type":["integer","null"],"format":"int64","description":"Bytes of raw HTTP traffic received from S3 during the restore,\nincluding protocol overhead."},"s3_sent_payload_bytes":{"type":["integer","null"],"format":"int64","description":"Bytes of decoded object payload sent to S3 during the restore."},"s3_sent_raw_bytes":{"type":["integer","null"],"format":"int64","description":"Bytes of raw HTTP traffic sent to S3 during the restore, including\nprotocol and signing overhead. Omit when traffic was not measured."},"server_id":{"type":"string","format":"uuid","description":"The server whose backup was restored."},"snapshot_id":{"type":["string","null"],"description":"Identifier of the snapshot that was restored. Omit on a failure that\nnever got as far as selecting a snapshot."},"type":{"type":"string","description":"The backup type that was restored (e.g. `tamanu-postgres`)."}}},"Version":{"type":"object","description":"A release version of the monitored software, with its publication\nstatus and changelog.","required":["id","created_at","updated_at","major","minor","patch","status","changelog"],"properties":{"changelog":{"type":"string","description":"Changelog text for this version, as Markdown."},"created_at":{"type":"string","format":"date-time","description":"When the version record was created."},"device_id":{"type":["string","null"],"format":"uuid","description":"The releaser device that published this version, if it was published\nby a device rather than created by an operator."},"id":{"type":"string","format":"uuid","description":"Unique identifier of the version."},"major":{"type":"integer","format":"int32","description":"Major version number."},"minor":{"type":"integer","format":"int32","description":"Minor version number."},"patch":{"type":"integer","format":"int32","description":"Patch version number."},"status":{"$ref":"#/components/schemas/VersionStatus","description":"Publication status: `draft`, `published`, or `yanked`."},"updated_at":{"type":"string","format":"date-time","description":"When the version record was last changed."}}},"VersionStatus":{"type":"string","description":"Publication status of a release version.","enum":["draft","published","yanked"]},"VersionStr":{"type":"string","description":"A semantic version string, e.g. `2.10.5`.","example":"2.10.5"},"ViewVersion":{"type":"object","description":"A release version as returned by the version-listing endpoints: the\nversion numbers, publication status, and changelog.","required":["id","major","minor","patch","status","changelog"],"properties":{"changelog":{"type":"string","description":"Changelog text for this version, as Markdown."},"id":{"type":"string","format":"uuid","description":"Unique identifier of the version."},"major":{"type":"integer","format":"int32","description":"Major version number."},"minor":{"type":"integer","format":"int32","description":"Minor version number."},"patch":{"type":"integer","format":"int32","description":"Patch version number."},"status":{"$ref":"#/components/schemas/VersionStatus","description":"Publication status: `draft`, `published`, or `yanked`."}}},"WorklistEntry":{"type":"object","description":"One replica the consumer device should currently maintain: an operator\ndeclaration expanded against a single server, carrying the snapshot to\nrestore and the repository coordinates to find it. S3 credentials and the\nrepository passphrase are obtained separately via\n`POST /restore-credentials`.","required":["replica_id","group_id","server_id","type","intent","name","params","storage","bucket","prefix","region"],"properties":{"bucket":{"type":"string","description":"Name of the S3 bucket holding the group's backup repository."},"group_id":{"type":"string","format":"uuid","description":"The server group whose backup repository holds the snapshot."},"intent":{"type":"string","description":"The restore intent this entry is for; one of the intents this device\nadvertised via `POST /restore-capabilities`."},"name":{"type":"string","description":"Operator-assigned label for the declaration."},"overdue_after_seconds":{"type":["integer","null"],"format":"int64","description":"Bound, in whole seconds, after which the replica counts as overdue;\n`null` means no bound. Interpreted per the intent's semantics: for a\nrun-once (`once`) intent, how long the latest snapshot may go without a\nhealthy verification report; for a standing replica, how stale its last\nhealthy report may be."},"params":{"type":"object","description":"Resolved parameter values for this replica: one key per parameter the\nintent advertises. Parameters the operator left unset carry the\nintent's declared default, or JSON `null` when there is none."},"prefix":{"type":"string","description":"Key prefix within the bucket under which the repository lives. Normally\nempty (the repository is at the bucket root)."},"region":{"type":"string","description":"AWS region of the bucket."},"replica_id":{"type":"string","format":"uuid","description":"Identifier of the declaration this entry was expanded from. Echo it\nback in `POST /restore-verification` reports."},"server_id":{"type":"string","format":"uuid","description":"The server whose backup should be restored."},"snapshot_at":{"type":["string","null"],"description":"When that snapshot was reported, as an RFC 3339 timestamp; `null` if\nunknown."},"snapshot_id":{"type":["string","null"],"description":"Identifier of the snapshot to restore — the latest successful backup\nfor this server and type. `null` when no successful backup is known\nyet."},"storage":{"type":"string","description":"Kind of storage backend. Always `\"s3\"`."},"type":{"type":"string","description":"The backup type to restore (e.g. `tamanu-postgres`)."}}}},"securitySchemes":{"admin-device":{"type":"mutualTLS","description":"mTLS client certificate for a device with the `admin` role."},"backup-restore-device":{"type":"mutualTLS","description":"mTLS client certificate for a device with the `backup-restore` role (or `admin`)."},"releaser-device":{"type":"mutualTLS","description":"mTLS client certificate for a device with the `releaser` role (or `admin`)."},"server-device":{"type":"mutualTLS","description":"mTLS client certificate for a device with the `server` role (or `admin`)."}}},"tags":[{"name":"artifacts","description":"Per-version artifact registration by releaser devices."},{"name":"backup","description":"Device backup credential minting, target config, capability registration, and run reporting."},{"name":"bestool","description":"Bestool SQL snippet read API."},{"name":"events","description":"Device-pushed events; rolled up into issues and incidents server-side."},{"name":"restore","description":"Managed restore replicas: consumer capability registration, worklist, and read-only restore credentials."},{"name":"servers","description":"Server registry — listing for the public, self-registration for server devices."},{"name":"statuses","description":"Heartbeat / status submissions from server devices."},{"name":"tags","description":"Key/value tags describing a server."},{"name":"versions","description":"Canopy release versions and their downloadable artifacts."}],"x-request-compression":{"accepted":["gzip","br","deflate","zstd"],"header":"Content-Encoding","recommended":true}}