Skip to content

GET /backup-targets

List the backup targets

Responses

  • 200 The targets
  • 403 An error per RFC 9457 Problem
Example bash
curl -X GET "$HUBTASK_URL/backup-targets" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /backup-targets

Create a backup target

Requires instance administrator rights (tenant-owned targets only when enabled). Credentials are stored encrypted and never returned. An unencrypted backup and the FTP protocol both require insecure_acknowledged: true. Auditable.

Request body application/json — BackupTargetCreate

NameTypeRequiredDescription
namestring required
kindBackupTargetKind
  • LOCAL
  • S3
  • SFTP
  • FTPS
  • FTP
  • WEBDAV
  • SMB
  • AZURE_BLOB
  • GCS
  • RCLONE
  • HTTP_PUT
required
configobject required
credentialsobject optionalOnly on create/update; stored encrypted and never returned.
encryption_modestring
  • AES256_GCM
  • NONE
  • default: "AES256_GCM"
optional
encryption_passphrasestring, or null optionalNot stored.
insecure_acknowledgedboolean
  • default: false
optionalMandatory for FTP or unencrypted storage (encryption_mode NONE).

Responses

Example bash
curl -X POST "$HUBTASK_URL/backup-targets" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "name": "string",  "kind": "LOCAL",  "config": {},  "credentials": {},  "encryption_mode": "AES256_GCM",  "encryption_passphrase": "string",  "insecure_acknowledged": false}'

DELETE /backup-targets/{targetId}

Remove a backup target from the workspace's configuration

Removes the target and its sealed credential. Nothing at the target is touched. The rule that Hubtask never deletes a file it did not write applies at least as strongly to the files it did write: the archives stay where they are, and an operator who wants them gone removes them at the target. A target a schedule still names is refused with 409 and the schedules named in the problem document. Deleting one silently would disarm a backup that runs every night, and the disarming would be discovered by whoever needed the archive. Deleting one that is not there is not an error. Auditable.

Path parameters

NameTypeRequiredDescription
targetIdstring (uuid) required

Responses

  • 204 Removed
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X DELETE "$HUBTASK_URL/backup-targets/{targetId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /backup-targets/{targetId}:test

Test the connection to the target

A write/read/delete probe. Runs through the GuardedClient (SSRF protection).

Path parameters

NameTypeRequiredDescription
targetIdstring (uuid) required

Responses

Example bash
curl -X POST "$HUBTASK_URL/backup-targets/{targetId}:test" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

GET /backup-targets/{targetId}/backups

List the backups present at the target

Reads the manifests at the target, not from the database. That means the listing works even after a total loss, as long as the target credentials are known.

It answers the workspace's own archives. A credential carrying admin:tenants - the installation operator's - also sees archives of workspaces this installation does not hold, such as an export written by another installation, which a NEW_TENANT restore takes; never an archive of another workspace that exists here.

Path parameters

NameTypeRequiredDescription
targetIdstring (uuid) required

Query parameters

NameTypeRequiredDescription
tenant_idstring (uuid) optional
refreshboolean
  • default: false
optionalBypass the cache and read again

Responses

  • 200 The archives at the target
  • 403 An error per RFC 9457 Problem
Example bash
curl -X GET "$HUBTASK_URL/backup-targets/{targetId}/backups" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

GET /backup-schedules

The schedules the workspace has

What runs on its own, and when it next will. Needs the permission that manages structure, or the auditor's read-only configuration permission - a schedule is what a workspace has decided will leave it every night, which is exactly what an auditor reads. next_run_at is absent for a schedule that is switched off and for one whose recurrence is exhausted. The second is not an error: the rule may be perfectly good and simply spent, and the schedule stays visible and editable rather than disappearing.

Responses

  • 200 The schedules
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X GET "$HUBTASK_URL/backup-schedules" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /backup-schedules

Create a backup schedule

RRULE-based (RFC 5545), with generational retention and a min_keep floor.

Request body application/json — BackupSchedule

NameTypeRequiredDescription
idstring (uuid) optional
target_idstring (uuid) required
scopeobject required
kindstring
  • INSTANCE
  • TENANT
  • HUB
  • COLLECTION
optional
idstring (uuid), or null optional
rrulestring required
timezonestring
  • default: "UTC"
optional
modestring
  • FULL
  • INCREMENTAL
  • default: "INCREMENTAL"
optional
full_rrulestring, or null optional
include_mediaboolean
  • default: true
optional
include_auditboolean
  • default: true
optional
trial_restoreboolean
  • default: true
optionalFollow every FULL run with an INSPECT restore of the archive it wrote, in the same job, and store the difference report on the run.
retentionBackupRetention optionalThe generation principle.
keep_lastinteger
  • default: 7
optional
keep_dailyinteger
  • default: 14
optional
keep_weeklyinteger
  • default: 8
optional
keep_monthlyinteger
  • default: 12
optional
keep_yearlyinteger
  • default: 3
optional
min_keepinteger
  • default: 3
  • minimum: 1
optional
notify_onarray of string optional
enabledboolean
  • default: true
optional
next_run_atstring (date-time), or null optional

Responses

Example bash
curl -X POST "$HUBTASK_URL/backup-schedules" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c",  "target_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c",  "scope": {},  "rrule": "FREQ=DAILY;BYHOUR=3;BYMINUTE=0",  "timezone": "UTC",  "mode": "INCREMENTAL",  "full_rrule": "FREQ=WEEKLY;BYDAY=SU",  "include_media": true,  "include_audit": true,  "trial_restore": true,  "retention": {},  "notify_on": [    "FAILURE"  ],  "enabled": true,  "next_run_at": "2026-09-16T10:00:00Z"}'

PATCH /backup-schedules/{scheduleId}

Change a schedule, or switch it off

Merge-patch: an absent key changes nothing. enabled is the field that matters most and the reason a DELETE is not the only answer - switching a schedule off keeps what somebody worked out about hours, zones and generations, and turning it back on costs one request rather than a form filled in again. Changing the rule, the zone or the switch recomputes when it is next owed, here rather than on every read. A rule with no occurrence inside a year stores no next moment and is not refused, for the reason creating one is not. The scope does not move: a schedule that changed from one hub to another would be a different schedule wearing an old identifier, and the archives already at the target would disagree with it. Auditable.

Path parameters

NameTypeRequiredDescription
scheduleIdstring (uuid) required

Headers

NameTypeRequiredDescription
If-Matchstring optionalThe ETag of the state last read (optimistic locking).

Request body application/merge-patch+json — BackupScheduleUpdate

NameTypeRequiredDescription
rrulestring optional
timezonestring optional
modestring
  • FULL
  • INCREMENTAL
optional
full_rrulestring, or null optional
include_mediaboolean optional
include_auditboolean optional
trial_restoreboolean optional
retentionBackupRetention optionalThe generation principle.
keep_lastinteger
  • default: 7
optional
keep_dailyinteger
  • default: 14
optional
keep_weeklyinteger
  • default: 8
optional
keep_monthlyinteger
  • default: 12
optional
keep_yearlyinteger
  • default: 3
optional
min_keepinteger
  • default: 3
  • minimum: 1
optional
notify_onarray of string optional
enabledboolean optionalOff keeps the rule and owes nothing: next_run_at is cleared, and switching it back on computes the next moment afresh rather than resuming a missed one.

Responses

Example bash
curl -X PATCH "$HUBTASK_URL/backup-schedules/{scheduleId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H 'If-Match: "<the ETag you read>"' \  -H "Content-Type: application/merge-patch+json" \  -d '{  "rrule": "FREQ=DAILY;BYHOUR=3;BYMINUTE=0",  "timezone": "string",  "mode": "FULL",  "full_rrule": "string",  "include_media": true,  "include_audit": true,  "trial_restore": true,  "retention": {},  "notify_on": [    "FAILURE"  ],  "enabled": true}'

DELETE /backup-schedules/{scheduleId}

Remove a schedule

The schedule stops existing and nothing else changes: the archives it produced stay at the target, and the runs it started stay in the log. Removing one that is not there is not an error. Auditable. Switching a schedule off is usually the right operation and this one is not - PATCH with enabled: false keeps the rule for the night somebody wants it back.

Path parameters

NameTypeRequiredDescription
scheduleIdstring (uuid) required

Responses

  • 204 Removed
  • 4XX An error per RFC 9457 Problem
Example bash
curl -X DELETE "$HUBTASK_URL/backup-schedules/{scheduleId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /backups

Start a backup immediately

The run is a job: this answers a JobRef whose result_url is the backup run, and the run's id is also the archive's id in the manifest at the target. One identifier for both, so that a caller who has a run has an archive and needs no mapping in between.

Request body application/json — BackupStart

NameTypeRequiredDescription
target_idstring (uuid) required
modestring
  • FULL
  • INCREMENTAL
  • default: "FULL"
optionalFULL by default, and deliberately: a run somebody asked for by hand is usually asked for because something is about to happen, and an incremental that turns out to have no parent at the target is the wrong thing to discover then.
include_mediaboolean
  • default: true
optional
include_auditboolean
  • default: true
optional

Responses

Example bash
curl -X POST "$HUBTASK_URL/backups" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "target_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c",  "mode": "FULL",  "include_media": true,  "include_audit": true}'

GET /backups/{backupId}

What one backup run did

The resource a started backup's result_url points at: what was written, where, how much of it, and whether it has been verified since. It is read from the database rather than from the target, so it is what a caller polls while the run is still going - /backup-targets/{targetId}/backups is the reading that survives losing the database.

Path parameters

NameTypeRequiredDescription
backupIdstring (uuid) required

Responses

Example bash
curl -X GET "$HUBTASK_URL/backups/{backupId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /backups/{backupId}:verify

Verify an archive at the target

Check the checksums and decryptability without restoring. The result is recorded on the run as verified_at and verify_ok.

Path parameters

NameTypeRequiredDescription
backupIdstring (uuid) required

Responses

Example bash
curl -X POST "$HUBTASK_URL/backups/{backupId}:verify" \  -H "Authorization: Bearer $HUBTASK_TOKEN"

POST /restores

Start a restore

A dry run (dry_run: true) with a report by default. Destructive modes (REPLACE_TENANT, INSTANCE) additionally require step-up authentication and the tenant name typed into confirmation. NEW_TENANT and INSTANCE create a workspace or cross all of them, so they are the installation operator's: the credential needs the admin:tenants scope as well, and is refused with access.insufficient_scope without it. A restore fires no automation, sends no notifications, and restores no tokens.

Request body application/json — RestoreRequest

NameTypeRequiredDescription
target_idstring (uuid) required
archive_idstring requiredWhere the archive lies at the target: the path of a BackupArchive as GET /backup-targets/{targetId}/backups lists it (and as RestoreRun.source_archive echoes it), not the archive_id beside it, which is the manifest's.
modestring
  • INSPECT
  • SELECTIVE
  • MERGE
  • REPLACE_TENANT
  • NEW_TENANT
  • INSTANCE
required
target_tenant_idstring (uuid), or null optionalThe tenant being restored into.
conflict_rulestring
  • SKIP
  • OVERWRITE
  • DUPLICATE
  • default: "SKIP"
optional
selectionobject optionalThe selection for SELECTIVE - container and/or item IDs.
container_idsarray of string (uuid) optional
item_idsarray of string (uuid) optional
decryption_passphrasestring, or null optional
dry_runboolean
  • default: true
optional
create_safety_backupboolean
  • default: true
optional
confirmationstring, or null optionalFor destructive modes, the exact tenant name.
step_up_tokenstring, or null optionalThe proof of a fresh, stronger authentication for a destructive mode: the token POST /auth/step-up answered.

Responses

Codes this operation names

Example bash
curl -X POST "$HUBTASK_URL/restores" \  -H "Authorization: Bearer $HUBTASK_TOKEN" \  -H "Content-Type: application/json" \  -d '{  "target_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c",  "archive_id": "string",  "mode": "INSPECT",  "target_tenant_id": "018f6e2a-2b7c-7c1e-9b1a-3d5e6f7a8b9c",  "conflict_rule": "SKIP",  "selection": {},  "decryption_passphrase": "string",  "dry_run": true,  "create_safety_backup": true,  "confirmation": "string",  "step_up_token": "string"}'

GET /restores/{restoreId}

What one restore did, or is about to do

The resource a started restore's result_url points at. It carries the report of the dry run - how many objects are new, overwritten, skipped and in conflict - which is what a caller reads before asking for the same restore without dry_run. It also names the safety copy taken before a destructive mode, so that the way back is a run identifier rather than a search at the target.

Path parameters

NameTypeRequiredDescription
restoreIdstring (uuid) required

Responses

Example bash
curl -X GET "$HUBTASK_URL/restores/{restoreId}" \  -H "Authorization: Bearer $HUBTASK_TOKEN"