# Job: Schedule Only

## Job: Schedule Only

**Audience:** Integrators who need a job that runs on a fixed interval and does not run immediately when the agent starts.

By default, a job with only a `schedule` waits for the first interval tick before running. There is no immediate execution on agent startup. Use this pattern when an initial run on startup would be disruptive or premature — for example, if dependent services need time to initialize, or if you want to avoid a burst of activity across a fleet every time agents restart. If you want the job to also run immediately on startup, use [Job: Schedule & Startup](/keeperpam/endpoint-privilege-manager/integrations/examples/job-schedule-and-startup.md) instead.

This example uses Windows paths. For Linux and macOS, apply the path and `osFilter` changes shown in the [Minimal Linux](/keeperpam/endpoint-privilege-manager/integrations/examples/job-minimal-linux.md) and [Minimal macOS](/keeperpam/endpoint-privilege-manager/integrations/examples/job-minimal-macos.md) examples.

## The Job JSON

```json
{
  "id": "my-tool",
  "name": "My Tool",
  "description": "Runs MyTool every 120 minutes. No immediate run on agent startup.",
  "enabled": true,

  "schedule": {
    "intervalMinutes": 120
  },

  "osFilter": {
    "windows": true,
    "linux": false,
    "macOS": false
  },

  "mqttTopics": {
    "allowedPublications": ["KeeperLogger"],
    "allowedSubscriptions": []
  },

  "parameters": [],

  "tasks": [
    {
      "id": "run-tool",
      "name": "Run tool",
      "ExecutionType": "Service",
      "command": "MyTool",
      "executablePath": "C:\\Program Files\\KeeperPrivilegeManager\\Jobs\\bin\\MyTool\\MyTool.exe",
      "arguments": "--keeper-api-base={KeeperApiBaseUrl}",
      "timeoutSeconds": 7200,
      "continueOnFailure": false,
      "scriptType": "Auto"
    }
  ]
}
```

## What to Change

<table data-header-hidden="false" data-header-sticky><thead><tr><th width="239.3333740234375">Field</th><th>What to Put Here</th></tr></thead><tbody><tr><td><code>id</code></td><td>A unique identifier for this job. Use hyphens — no underscores. Filename must match: <code>my-tool.json</code> for <code>"id": "my-tool"</code>.</td></tr><tr><td><code>name</code></td><td>A human-readable name shown in logs and the admin view.</td></tr><tr><td><code>schedule.intervalMinutes</code></td><td>Minutes between runs. <code>120</code> = every two hours. This is elapsed time from the previous run completing, not a clock-aligned interval — <code>120</code> does not mean "on the hour, every two hours."</td></tr><tr><td><code>tasks[0].command</code></td><td>The name of your binary without a path or extension.</td></tr><tr><td><code>tasks[0].executablePath</code></td><td>Full path to your binary on the endpoint.</td></tr><tr><td><code>tasks[0].arguments</code></td><td>Flags your binary accepts. Keep <code>{KeeperApiBaseUrl}</code>.</td></tr><tr><td><code>tasks[0].timeoutSeconds</code></td><td>Maximum run time. Set this to a value that comfortably exceeds your tool's longest expected run, but not so long that a hung process goes undetected.</td></tr></tbody></table>

## How This Works

The absence of an `events` array is what makes this schedule-only. The job has no Startup trigger, so when the agent starts, it loads the job and begins the interval timer — but does not immediately execute the task. The first run occurs after `intervalMinutes` minutes from when the scheduler initializes.

`intervalMinutes` measures time between runs, not wall-clock alignment. If `intervalMinutes` is `120` and the agent starts at 09:47, the first run is at approximately 11:47, not at 10:00 or 12:00. If clock-aligned scheduling matters for your use case, use a `cronExpression` schedule instead:

```json
"schedule": {
  "cronExpression": "0 */2 * * *"
}
```

This runs at the top of every even hour (00:00, 02:00, 04:00, and so on) regardless of when the agent started.

`timeoutSeconds` is set to `7200` here to match the `120`-minute interval. In general, set the timeout to be slightly longer than the longest run you expect — long enough to let the task finish, short enough to surface a hung process before the next scheduled run begins.

## Before You Deploy

1. Deploy the binary to `executablePath` on the endpoint before registering the job.
2. Confirm the `intervalMinutes` value reflects the cadence you actually want, not just a placeholder.
3. Filename must match `id`.

## Deploy

Validate before saving:

```powershell
Invoke-RestMethod -Method Post `
  -Uri "https://127.0.0.1:6889/api/Jobs/validate" `
  -ContentType "application/json" `
  -Certificate $adminClientCert `
  -Body (Get-Content -Raw .\my-tool.json)
```

Create the job:

```powershell
Invoke-RestMethod -Method Post `
  -Uri "https://127.0.0.1:6889/api/Jobs" `
  -ContentType "application/json" `
  -Certificate $adminClientCert `
  -Body (Get-Content -Raw .\my-tool.json)
```

Confirm the job is registered and check its state:

```powershell
Invoke-RestMethod -Method Get `
  -Uri "https://127.0.0.1:6889/api/Jobs/my-tool" `
  -Certificate $adminClientCert
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.keeper.io/keeperpam/endpoint-privilege-manager/integrations/examples/job-schedule-only.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
