PBN.LTD API docs
View as Markdown

Cron jobs

Scheduled tasks for a site: WordPress scheduled tasks, a PHP script, a web address or a command, every 5 minutes at most, stopped after 15 minutes, as the site's own user. Free on every plan; 5 per site, 25 per account. Times are UTC.

List a site's cron jobs

GET/api/v1/sites/{site_id}/cron-jobs

Scope cron:read

Every cron job of the site with its schedule (as written and in words, UTC), what it runs, whether it is on, why it is paused if it is (the site is frozen or suspended, it timed out 3 times in a row, or our team paused it), its next 3 run times and its last result. kinds lists what this site type may run. limits shows the per-site and per-account counts.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).

Example

curl -s "https://app.pbn.ltd/api/v1/sites/123/cron-jobs" \
  -H "Authorization: Bearer $PBN_API_KEY"
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/cron-jobs", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/cron-jobs", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());

Response

200

{
  "data": {
    "jobs": [
      {
        "id": 42,
        "site_id": 1234,
        "name": "WordPress scheduled tasks",
        "kind": "wpcron",
        "kind_label": "Run WordPress scheduled tasks",
        "target": "",
        "command": "wp cron event run --due-now",
        "summary": "WordPress scheduled tasks (wp cron event run --due-now)",
        "schedule": "7,22,37,52 * * * *",
        "schedule_text": "Every 15 minutes (at :07, :22 ...)",
        "schedule_spec": {
          "mode": "minutes",
          "every": 15
        },
        "enabled": true,
        "state": "active",
        "state_label": "On",
        "state_reason": "",
        "paused_reason": null,
        "wp_pseudo_cron_off": true,
        "next_runs": [
          "2026-09-26T10:22:00Z",
          "2026-09-26T10:37:00Z",
          "2026-09-26T10:52:00Z"
        ],
        "last_run_at": "2026-09-26T10:07:00Z",
        "last_status": "ok",
        "last_exit_code": 0,
        "last_duration_seconds": 1.42,
        "run_now_pending": false,
        "created_at": "2026-09-26T09:00:00Z",
        "updated_at": "2026-09-26T09:00:00Z"
      }
    ],
    "limits": {
      "per_site": 5,
      "per_account": 25,
      "site_count": 1,
      "account_count": 3,
      "min_interval_minutes": 5,
      "max_run_minutes": 15,
      "timezone": "UTC"
    },
    "kinds": [
      "wpcron",
      "php",
      "url",
      "command"
    ],
    "available": true,
    "paused_reason": ""
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

List every cron job on the account

GET/api/v1/cron-jobs

Scope cron:read

All cron jobs on every site of the account (at most 25), each with its site id and domain.

Example

curl -s "https://app.pbn.ltd/api/v1/cron-jobs" \
  -H "Authorization: Bearer $PBN_API_KEY"
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/cron-jobs", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/cron-jobs", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());

Response

200

{
  "data": {
    "jobs": [
      {
        "id": 42,
        "site_id": 1234,
        "name": "WordPress scheduled tasks",
        "kind": "wpcron",
        "kind_label": "Run WordPress scheduled tasks",
        "target": "",
        "command": "wp cron event run --due-now",
        "summary": "WordPress scheduled tasks (wp cron event run --due-now)",
        "schedule": "7,22,37,52 * * * *",
        "schedule_text": "Every 15 minutes (at :07, :22 ...)",
        "schedule_spec": {
          "mode": "minutes",
          "every": 15
        },
        "enabled": true,
        "state": "active",
        "state_label": "On",
        "state_reason": "",
        "paused_reason": null,
        "wp_pseudo_cron_off": true,
        "next_runs": [
          "2026-09-26T10:22:00Z",
          "2026-09-26T10:37:00Z",
          "2026-09-26T10:52:00Z"
        ],
        "last_run_at": "2026-09-26T10:07:00Z",
        "last_status": "ok",
        "last_exit_code": 0,
        "last_duration_seconds": 1.42,
        "run_now_pending": false,
        "created_at": "2026-09-26T09:00:00Z",
        "updated_at": "2026-09-26T09:00:00Z",
        "domain": "example.com"
      }
    ],
    "count": 1,
    "per_account": 25
  }
}

Errors: rate_limited, scope_missing, unauthorized

One cron job with its last runs

GET/api/v1/sites/{site_id}/cron-jobs/{cron_id}

Scope cron:read

The job and its last 10 runs: when, how long, the exit code, and the output (the first 1,000 and last 7,000 bytes of long output are kept).

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
cron_idpathintegeryesThe cron job id (see the list).

Example

curl -s "https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42" \
  -H "Authorization: Bearer $PBN_API_KEY"
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());

Response

200

{
  "data": {
    "id": 42,
    "site_id": 1234,
    "name": "WordPress scheduled tasks",
    "kind": "wpcron",
    "kind_label": "Run WordPress scheduled tasks",
    "target": "",
    "command": "wp cron event run --due-now",
    "summary": "WordPress scheduled tasks (wp cron event run --due-now)",
    "schedule": "7,22,37,52 * * * *",
    "schedule_text": "Every 15 minutes (at :07, :22 ...)",
    "schedule_spec": {
      "mode": "minutes",
      "every": 15
    },
    "enabled": true,
    "state": "active",
    "state_label": "On",
    "state_reason": "",
    "paused_reason": null,
    "wp_pseudo_cron_off": true,
    "next_runs": [
      "2026-09-26T10:22:00Z",
      "2026-09-26T10:37:00Z",
      "2026-09-26T10:52:00Z"
    ],
    "last_run_at": "2026-09-26T10:07:00Z",
    "last_status": "ok",
    "last_exit_code": 0,
    "last_duration_seconds": 1.42,
    "run_now_pending": false,
    "created_at": "2026-09-26T09:00:00Z",
    "updated_at": "2026-09-26T09:00:00Z",
    "runs": [
      {
        "id": 881,
        "status": "ok",
        "status_label": "Finished",
        "trigger": "schedule",
        "started_at": "2026-09-26T10:07:00Z",
        "finished_at": "2026-09-26T10:07:02Z",
        "duration_seconds": 1.42,
        "exit_code": 0,
        "output": "Success: Executed a total of 3 cron events.\n",
        "output_bytes": 46,
        "output_truncated": false,
        "note": ""
      }
    ]
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

The last runs of a cron job

GET/api/v1/sites/{site_id}/cron-jobs/{cron_id}/runs

Scope cron:read

Newest first. status: ok, failed (non-zero exit code), timeout (stopped at 15 minutes), skipped (the previous run was still going, or the server was paused) or error (could not start). trigger: schedule or manual (Run now).

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
cron_idpathintegeryesThe cron job id (see the list).
limitqueryintegernoHow many (newest first, at most 20 are kept). Default: 20.

Example

curl -s "https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42/runs" \
  -H "Authorization: Bearer $PBN_API_KEY"
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42/runs", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42/runs", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());

Response

200

{
  "data": {
    "runs": [
      {
        "id": 881,
        "status": "ok",
        "status_label": "Finished",
        "trigger": "schedule",
        "started_at": "2026-09-26T10:07:00Z",
        "finished_at": "2026-09-26T10:07:02Z",
        "duration_seconds": 1.42,
        "exit_code": 0,
        "output": "Success: Executed a total of 3 cron events.\n",
        "output_bytes": 46,
        "output_truncated": false,
        "note": ""
      }
    ]
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized

Add a cron job

POST/api/v1/sites/{site_id}/cron-jobs

Scope cron:write

Adds a job and puts it on the site's server within about a minute. It runs as the site's own user, in the site folder, with the site's PHP version, and is stopped after 15 minutes. Refused with limit_reached at 5 jobs on the site or 25 on the account, and with validation_failed for a schedule more often than every 5 minutes, a multi-line command, or a script path outside the site folder.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
namebodystringnoA name you recognise (up to 80 characters).
kindbodystring (one of: wpcron, php, url, command)noWhat runs. wpcron: WordPress scheduled tasks (wp cron event run --due-now, WordPress sites only); php: a PHP script, target = path inside the site folder ending in .php (not for Static HTML); url: open a web address, target = http(s) address; command: a shell command, target = the command (one line, up to 1,000 characters). Required when creating.
targetbodystringnoThe script path, web address or command (see kind). Not used for wpcron.
schedule_modebodystring (one of: minutes, hourly, daily, weekly, advanced)noHow the schedule is given. minutes: every N minutes (every); hourly: once an hour at minute; daily: at hour:minute UTC; weekly: on weekday at hour:minute UTC; advanced: a 5-field cron expression. Required when creating.
everybodyinteger (one of: 5, 10, 15, 20, 30)nominutes mode: run every 5, 10, 15, 20 or 30 minutes. We pick a fixed start offset inside each window (e.g. :07, :22, :37, :52), so sites do not all start at the same second.
minutebodyintegernohourly / daily / weekly: the minute (0-59). Default: a fixed minute picked for the job.
hourbodyintegernodaily / weekly: the hour, UTC.
weekdaybodyintegernoweekly: 0 = Sunday, 1 = Monday ... 6 = Saturday.
expressionbodystringnoadvanced: five fields "minute hour day-of-month month day-of-week", UTC, e.g. "*/15 * * * *" or "30 2 * * 1-5". It may never run more often than every 5 minutes (checked across the whole hour, including from :55 to :00). A "*/N" minute field is moved to a fixed offset.
enabledbodybooleannoSwitch the job on (default) or off.
wp_pseudo_cron_offbodybooleannowpcron only: also stop WordPress running its scheduled tasks on page visits (sets DISABLE_WP_CRON in wp-config.php, only if it is not set already; taken out again when the job is deleted or switched off). Default true for wpcron.

Example

curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/cron-jobs" \
  -H "Authorization: Bearer $PBN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "WordPress scheduled tasks", "kind": "wpcron", "schedule_mode": "minutes", "every": 15, "wp_pseudo_cron_off": true}'
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/cron-jobs", headers=headers, json={"name": "WordPress scheduled tasks", "kind": "wpcron", "schedule_mode": "minutes", "every": 15, "wp_pseudo_cron_off": True}, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/cron-jobs", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
  body: JSON.stringify({"name": "WordPress scheduled tasks", "kind": "wpcron", "schedule_mode": "minutes", "every": 15, "wp_pseudo_cron_off": true})
});
console.log(res.status, await res.json());

Response

201

{
  "data": {
    "id": 42,
    "site_id": 1234,
    "name": "WordPress scheduled tasks",
    "kind": "wpcron",
    "kind_label": "Run WordPress scheduled tasks",
    "target": "",
    "command": "wp cron event run --due-now",
    "summary": "WordPress scheduled tasks (wp cron event run --due-now)",
    "schedule": "7,22,37,52 * * * *",
    "schedule_text": "Every 15 minutes (at :07, :22 ...)",
    "schedule_spec": {
      "mode": "minutes",
      "every": 15
    },
    "enabled": true,
    "state": "active",
    "state_label": "On",
    "state_reason": "",
    "paused_reason": null,
    "wp_pseudo_cron_off": true,
    "next_runs": [
      "2026-09-26T10:22:00Z",
      "2026-09-26T10:37:00Z",
      "2026-09-26T10:52:00Z"
    ],
    "last_run_at": "2026-09-26T10:07:00Z",
    "last_status": "ok",
    "last_exit_code": 0,
    "last_duration_seconds": 1.42,
    "run_now_pending": false,
    "created_at": "2026-09-26T09:00:00Z",
    "updated_at": "2026-09-26T09:00:00Z"
  }
}

Errors: conflict, limit_reached, not_found, rate_limited, scope_missing, unauthorized, validation_failed

Change a cron job

PATCH/api/v1/sites/{site_id}/cron-jobs/{cron_id}

Scope cron:write

Change any of the fields; the others stay as they are. To change the schedule give schedule_mode with its fields. Switching a job on again (enabled: true) also clears a pause after repeated timeouts. On the server within about a minute.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
cron_idpathintegeryesThe cron job id (see the list).
namebodystringnoA name you recognise (up to 80 characters).
kindbodystring (one of: wpcron, php, url, command)noWhat runs. wpcron: WordPress scheduled tasks (wp cron event run --due-now, WordPress sites only); php: a PHP script, target = path inside the site folder ending in .php (not for Static HTML); url: open a web address, target = http(s) address; command: a shell command, target = the command (one line, up to 1,000 characters). Required when creating.
targetbodystringnoThe script path, web address or command (see kind). Not used for wpcron.
schedule_modebodystring (one of: minutes, hourly, daily, weekly, advanced)noHow the schedule is given. minutes: every N minutes (every); hourly: once an hour at minute; daily: at hour:minute UTC; weekly: on weekday at hour:minute UTC; advanced: a 5-field cron expression. Required when creating.
everybodyinteger (one of: 5, 10, 15, 20, 30)nominutes mode: run every 5, 10, 15, 20 or 30 minutes. We pick a fixed start offset inside each window (e.g. :07, :22, :37, :52), so sites do not all start at the same second.
minutebodyintegernohourly / daily / weekly: the minute (0-59). Default: a fixed minute picked for the job.
hourbodyintegernodaily / weekly: the hour, UTC.
weekdaybodyintegernoweekly: 0 = Sunday, 1 = Monday ... 6 = Saturday.
expressionbodystringnoadvanced: five fields "minute hour day-of-month month day-of-week", UTC, e.g. "*/15 * * * *" or "30 2 * * 1-5". It may never run more often than every 5 minutes (checked across the whole hour, including from :55 to :00). A "*/N" minute field is moved to a fixed offset.
enabledbodybooleannoSwitch the job on (default) or off.
wp_pseudo_cron_offbodybooleannowpcron only: also stop WordPress running its scheduled tasks on page visits (sets DISABLE_WP_CRON in wp-config.php, only if it is not set already; taken out again when the job is deleted or switched off). Default true for wpcron.

Example

curl -s -X PATCH "https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42" \
  -H "Authorization: Bearer $PBN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind": "php", "target": "scripts/nightly.php", "schedule_mode": "daily", "hour": 3, "minute": 20}'
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.patch("https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42", headers=headers, json={"kind": "php", "target": "scripts/nightly.php", "schedule_mode": "daily", "hour": 3, "minute": 20}, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42", {
  method: "PATCH",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
  body: JSON.stringify({"kind": "php", "target": "scripts/nightly.php", "schedule_mode": "daily", "hour": 3, "minute": 20})
});
console.log(res.status, await res.json());

Response

200

{
  "data": {
    "id": 42,
    "site_id": 1234,
    "name": "WordPress scheduled tasks",
    "kind": "wpcron",
    "kind_label": "Run WordPress scheduled tasks",
    "target": "",
    "command": "wp cron event run --due-now",
    "summary": "WordPress scheduled tasks (wp cron event run --due-now)",
    "schedule": "7,22,37,52 * * * *",
    "schedule_text": "Every 15 minutes (at :07, :22 ...)",
    "schedule_spec": {
      "mode": "minutes",
      "every": 15
    },
    "enabled": true,
    "state": "active",
    "state_label": "On",
    "state_reason": "",
    "paused_reason": null,
    "wp_pseudo_cron_off": true,
    "next_runs": [
      "2026-09-26T10:22:00Z",
      "2026-09-26T10:37:00Z",
      "2026-09-26T10:52:00Z"
    ],
    "last_run_at": "2026-09-26T10:07:00Z",
    "last_status": "ok",
    "last_exit_code": 0,
    "last_duration_seconds": 1.42,
    "run_now_pending": false,
    "created_at": "2026-09-26T09:00:00Z",
    "updated_at": "2026-09-26T09:00:00Z"
  }
}

Errors: conflict, not_found, rate_limited, scope_missing, unauthorized, validation_failed

Delete a cron job

DELETE/api/v1/sites/{site_id}/cron-jobs/{cron_id}

Scope cron:write · destructive

Deletes the job and its run history, and takes it off the server within about a minute. A run already going finishes.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
cron_idpathintegeryesThe cron job id (see the list).
confirmbodybooleanyesMust be true: the job and its run history are deleted.

Example

curl -s -X DELETE "https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42" \
  -H "Authorization: Bearer $PBN_API_KEY"
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.delete("https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42", {
  method: "DELETE",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());

Response

200

{
  "data": {
    "deleted": true,
    "id": 42
  }
}

Errors: not_found, rate_limited, scope_missing, unauthorized, validation_failed

Run a cron job now

POST/api/v1/sites/{site_id}/cron-jobs/{cron_id}/run

Scope cron:write

Starts the job on its server at once (once a minute per job, 10 times in 10 minutes per account). The run reports back like a scheduled one: read it with cron.runs a few seconds later (trigger "manual"). Refused while the site is frozen or suspended.

Parameters

NameInTypeRequiredDescription
site_idpathintegeryesThe site id (see GET /sites).
cron_idpathintegeryesThe cron job id (see the list).

Example

curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42/run" \
  -H "Authorization: Bearer $PBN_API_KEY"
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42/run", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/cron-jobs/42/run", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());

Response

200

{
  "data": {
    "started": true,
    "message": "Started.",
    "job": {
      "id": 42,
      "site_id": 1234,
      "name": "WordPress scheduled tasks",
      "kind": "wpcron",
      "kind_label": "Run WordPress scheduled tasks",
      "target": "",
      "command": "wp cron event run --due-now",
      "summary": "WordPress scheduled tasks (wp cron event run --due-now)",
      "schedule": "7,22,37,52 * * * *",
      "schedule_text": "Every 15 minutes (at :07, :22 ...)",
      "schedule_spec": {
        "mode": "minutes",
        "every": 15
      },
      "enabled": true,
      "state": "active",
      "state_label": "On",
      "state_reason": "",
      "paused_reason": null,
      "wp_pseudo_cron_off": true,
      "next_runs": [
        "2026-09-26T10:22:00Z",
        "2026-09-26T10:37:00Z",
        "2026-09-26T10:52:00Z"
      ],
      "last_run_at": "2026-09-26T10:07:00Z",
      "last_status": "ok",
      "last_exit_code": 0,
      "last_duration_seconds": 1.42,
      "run_now_pending": false,
      "created_at": "2026-09-26T09:00:00Z",
      "updated_at": "2026-09-26T09:00:00Z"
    }
  }
}

Errors: conflict, not_found, rate_limited, scope_missing, unauthorized