Backup Schedules

7 views Markdown

The nine endpoints that set up backup schedules and manage the backups taken.

Overview

These nine endpoints handle two things. Five of them set up the schedules: what content, how often, to which target. The other four deal with the backups already taken.

A schedule is only an intent. What actually makes a backup is the scheduled job that picks the work up when its turn comes. Writing calls do not return a result straight away: the job queues, a record opens, and the status moves along over time.

Where backups go is a separate matter. Targets are set up through their own endpoints, and the storage_id here merely points at one of them.

Reference

Listing the Schedules

get/api/v1/admin/settings/backup/schedules
Settings/GetBackupSchedules admin

Returns the backup schedules defined.

Query 1
searchstringSearches by name.
Response fields data[] — 14
idintThe schedule id.
namestringThe schedule name.
statusstringWhether it is on or off.
frequencystringHow often it runs: hourly, daily, weekly, monthly.
run_timestringThe time of day it runs.
run_dowint | nullThe day of the week. Only meaningful at weekly frequency.
run_domint | nullThe day of the month. Only meaningful at monthly frequency.
contentsstring[]What goes into the backup: database, files, uploads.
storage_idintThe target it gets sent to. Zero means the server itself.
keep_localintWhether a copy also stays on the server.
retention_countintHow many backups are kept.
notify_adminintWhether an administrator gets told.
next_run_atstring | nullWhen it next runs.
created_atstring | nullWhen it was created.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/settings/backup/schedules' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/settings/backup/schedules', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/settings/backup/schedules');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The next run time reads full on an OFF schedule too, so read the status as well.
$due = array_filter(
    Api::Settings()->GetBackupSchedules()['data'],
    fn ($s) => $s['status'] === 'enabled',
);

Creating a Schedule

post/api/v1/admin/settings/backup/schedules
Settings/CreateBackupSchedule admin born switched off

Sets up a new backup schedule.

Body 11
namestringreqThe schedule name. Up to 150 characters.
contentsstring[]reqWhat goes into the backup: database, files, uploads. At least one is needed.
statusintSwitches the schedule on. Left out, it is born switched off.
frequencystringHow often it runs: hourly, daily, weekly, monthly. Daily by default.
run_timestringThe time of day it runs. Three in the morning by default.
run_dowintThe day of the week (0-6).
run_domintThe day of the month (1-31).
storage_idintThe target it gets sent to. Zero means the server itself.
keep_localintLeaves a copy on the server. Forced on when the target is the server itself.
retention_countintHow many backups to keep. Seven by default.
notify_adminintTells an administrator.
Response fields 201 — data
dataobjectThe schedule created. Same shape as a list item.
Errors 3
name_required422The name is empty.
contents_empty422No content was chosen.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/settings/backup/schedules' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Gecelik veritabani","frequency":"daily","run_time":"03:00","contents":["database"],"status":1}'
const res = await fetch('https://panel.example.com/api/v1/admin/settings/backup/schedules', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Nightly database',
    frequency: 'daily',
    run_time: '03:00',
    contents: ['database'],
    status: 1,
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/settings/backup/schedules');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'name'      => 'Nightly database',
        'frequency' => 'daily',
        'run_time'  => '03:00',
        'contents'  => ['database'],
        'status'    => 1,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// LEAVE THE STATUS OUT and the schedule is born off, so no night ever runs it.
Api::Settings()->CreateBackupSchedule([
    'name'     => 'Nightly database',
    'contents' => ['database'],
    'status'   => 1,
]);

Reading One Schedule

get/api/v1/admin/settings/backup/schedules/{id}
Settings/GetBackupSchedule admin

Returns a single schedule.

Response fields data — 14
idintThe schedule id.
namestringThe schedule name.
statusstringWhether it is on or off.
frequencystringHow often it runs: hourly, daily, weekly, monthly.
run_timestringThe time of day it runs.
run_dowint | nullThe day of the week. Only meaningful at weekly frequency.
run_domint | nullThe day of the month. Only meaningful at monthly frequency.
contentsstring[]What goes into the backup: database, files, uploads.
storage_idintThe target it gets sent to. Zero means the server itself.
keep_localintWhether a copy also stays on the server.
retention_countintHow many backups are kept.
notify_adminintWhether an administrator gets told.
next_run_atstring | nullWhen it next runs.
created_atstring | nullWhen it was created.
Errors 2
not_found404No such schedule.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/settings/backup/schedules/4' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch(`https://panel.example.com/api/v1/admin/settings/backup/schedules/${id}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/settings/backup/schedules/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// When the day fields do not MATCH the frequency they read full and do nothing.
$s = Api::Settings()->GetBackupSchedule(['id' => $id])['data'];
$day = $s['frequency'] === 'weekly' ? $s['run_dow'] : null;

Updating a Schedule

patch/api/v1/admin/settings/backup/schedules/{id}
Settings/UpdateBackupSchedule admin

Changes a schedule, recomputing the next run when a timing field moves.

Body 11
namestringreqThe schedule name. Up to 150 characters.
contentsstring[]reqWhat goes into the backup: database, files, uploads. At least one is needed.
statusintSwitches the schedule on. Left out, it is born switched off.
frequencystringHow often it runs: hourly, daily, weekly, monthly. Daily by default.
run_timestringThe time of day it runs. Three in the morning by default.
run_dowintThe day of the week (0-6).
run_domintThe day of the month (1-31).
storage_idintThe target it gets sent to. Zero means the server itself.
keep_localintLeaves a copy on the server. Forced on when the target is the server itself.
retention_countintHow many backups to keep. Seven by default.
notify_adminintTells an administrator.
Response fields data — 14
dataobjectThe schedule as it now stands. Same shape as a list item.
next_run_atstringThe recomputed next run. Moving the frequency, the time of day, the weekday or the day of the month reschedules the job, so read the new moment here instead of keeping the old one.
Errors 4
not_found404No such schedule.
name_required422The name is empty.
contents_empty422No content was chosen.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/settings/backup/schedules/4' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"run_time":"04:30","retention_count":14}'
const res = await fetch(`https://panel.example.com/api/v1/admin/settings/backup/schedules/${id}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ run_time: '04:30', retention_count: 14 }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/settings/backup/schedules/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['run_time' => '04:30', 'retention_count' => 14]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// What you leave out is KEPT; moving the time shifts when the schedule next runs.
Api::Settings()->UpdateBackupSchedule([
    'id'       => $id,
    'run_time' => '04:30',
]);

Deleting a Schedule

delete/api/v1/admin/settings/backup/schedules/{id}
Settings/DeleteBackupSchedule admin

Removes a schedule and unhooks the backups it made.

Response fields data — 2
deletedboolWhether the delete ran.
idintThe id of the schedule removed.
Errors 2
not_found404No such schedule.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/settings/backup/schedules/4' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/settings/backup/schedules/${id}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/settings/backup/schedules/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The backups are NOT removed, only unhooked; their files stay where they are.
Api::Settings()->DeleteBackupSchedule(['id' => $id]);

Listing the Backups

get/api/v1/admin/settings/backup/backups
Settings/GetBackups admin

Returns the history of backups taken.

Query 1
searchstringSearches the records.
Response fields data[] — 21
idintThe backup id.
schedule_idint | nullThe schedule that made it. Empty for a backup taken by hand.
storage_idintThe target it went to.
keep_localintWhether a copy stayed on the server.
contentsstringWhat went into the backup.
labelstringThe backup label.
file_namestringThe archive name.
file_sizeintThe archive size.
local_pathstring | nullWhere it sits on the server.
remote_pathstring | nullWhere it sits on the target.
upload_statusstringHow the upload went. Separate from the backup's own status.
upload_errorstring | nullWhy the upload failed.
uploaded_atstring | nullWhen the upload finished.
statusstringThe backup status. Pending, running, ready, failed or expired.
progressstring | nullHow far it got.
error_messagestring | nullWhy the backup failed.
duration_secondsintHow long it took.
started_atstring | nullWhen it started.
completed_atstring | nullWhen it finished.
cdatestringWhen the record was opened.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/settings/backup/backups' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/settings/backup/backups', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/settings/backup/backups');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A ready backup can STILL have lost its offsite copy, so read both statuses together.
$risky = array_filter(
    Api::Settings()->GetBackups()['data'],
    fn ($b) => $b['status'] === 'ready' && $b['upload_status'] === 'failed',
);

Taking a Backup by Hand

post/api/v1/admin/settings/backup/backups
Settings/CreateManualBackup admin gets queued

Queues a backup job without waiting for a schedule.

Body 3
contentsstring[]reqWhat goes into the backup: database, files, uploads.
storage_idintThe target it gets sent to. Zero means the server itself.
keep_localintLeaves a copy on the server.
Response fields 201 — data — 2
queuedboolWhether the job got queued. It does not mean the backup finished.
backup_idintThe id of the backup record opened.
Errors 4
contents_empty422No content was chosen.
system_disabled422The backup system is off.
already_running422A backup is already running.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/settings/backup/backups' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"contents":["database","files"],"storage_id":0}'
const res = await fetch('https://panel.example.com/api/v1/admin/settings/backup/backups', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    contents: ['database', 'files'],
    storage_id: 0,
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/settings/backup/backups');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'contents'   => ['database', 'files'],
        'storage_id' => 0,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Only ONE backup runs at a time: a request landing in the nightly window is refused.
$id = Api::Settings()->CreateManualBackup([
    'contents' => ['database'],
])['data']['backup_id'];

Retrying a Backup

post/api/v1/admin/settings/backup/backups/{id}/retry
Settings/RetryBackup admin

Puts a failed backup back in the queue.

Body
No body is needed, send an empty one. The backup comes from the id in the path.
Response fields data — 2
requeuedboolWhether the job went back in the queue.
backup_idintThe backup id.
Errors 6
invalid_id422The id is not valid.
not_found404No such backup.
not_failed422The backup did not fail. Only failed ones can be retried.
system_disabled422The backup system is off.
already_running422A backup is already running.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/settings/backup/backups/91/retry' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/settings/backup/backups/${id}/retry`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/settings/backup/backups/' . $id . '/retry');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A backup whose upload failed while it stayed READY cannot be retried from here.
if ($backup['status'] === 'failed')
    Api::Settings()->RetryBackup(['id' => $backup['id']]);

Deleting a Backup

delete/api/v1/admin/settings/backup/backups/{id}
Settings/DeleteBackup admin the files go too

Removes a backup record, its queued job and its files.

Response fields data — 2
deletedboolWhether the delete ran.
idintThe id of the backup removed.
Errors 4
invalid_id422The id is not valid.
not_found404No such backup.
backup_running422The backup is running.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/settings/backup/backups/91' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/settings/backup/backups/${id}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/settings/backup/backups/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A running backup cannot go; wait for it to end, as NO endpoint cancels one.
Api::Settings()->DeleteBackup(['id' => $id]);

Pitfalls

A new schedule is born switched off

Leave the status field out and the schedule is created switched off. It shows in the list, its next run time even reads full, and no night ever runs it. Believing it works and finding no backups months later starts here, so send the status along when you create it.

Only one backup runs at a time

While a backup runs, the take-by-hand and retry calls are refused. A script that lands in the nightly window reads that as a failure. The answer describes a busy moment rather than a lasting problem, so try again a little later.

There are two separate statuses

A backup's own status and its upload status are separate fields. One can read as ready while its offsite copy failed: the archive sits on the server and never reached the target. On the day you lose that server the second field is what matters, so read both together.

Retry is only for backups that failed

The retry endpoint refuses any backup whose status is not failed. One whose upload failed while it stayed ready cannot pass either, because what is missing there is the offsite copy rather than the backup. That case calls for taking a fresh backup.

Deleting a schedule does not delete its backups

When a schedule goes, the backups it made are unhooked and stay put. Their files remain and no retention count counts them for anyone any more. Freeing space means deleting those backups one by one, and deleting a backup cancels its queued job and removes its files.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.