# Job: Schedule & Startup

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

This is the most common pattern for scanners and reporters — run once at startup to get a baseline result immediately, then continue on schedule. The difference from [Job: Schedule Only](/keeperpam/endpoint-privilege-manager/integrations/examples/job-schedule-only.md) is the addition of an `events` entry with `eventType: Startup`. Both triggers are independent: the Startup event fires once when the agent loads jobs, and the interval timer runs from that point forward regardless of when the Startup run completed.

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 at agent startup and every 120 minutes thereafter.",
  "enabled": true,

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

  "events": [
    { "eventType": "Startup" }
  ],

  "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="233.333251953125">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 scheduled runs after startup. Does not affect when the Startup event fires.</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 per execution. The Startup run and each interval run each get this full budget independently.</td></tr></tbody></table>

## How This Works

Adding `"events": [{ "eventType": "Startup" }]` is the only change from the schedule-only pattern. Everything else is identical.

When the agent starts, it loads jobs and then fires the Startup event for any job that declares it. The Startup run and the interval timer are independent — the interval does not wait for the Startup run to complete before it begins counting. In practice this means the first interval run may occur while the Startup run is still in progress if your tool runs close to the interval boundary. Design your tool to handle concurrent executions, or set `intervalMinutes` to a value comfortably longer than the worst-case run time.

The Startup event fires once per agent start. It does not re-fire after job reloads or policy updates unless the agent service itself restarts.

KeeperLogger is ready before Startup jobs execute — the agent waits for its logging infrastructure to initialize before triggering Startup events, so early log messages from a Startup run are not lost.

### Before You Deploy

1. Deploy the binary to `executablePath` on the endpoint before registering the job.
2. Consider whether a Startup run is safe in your environment. On a large fleet, every agent restart triggers a simultaneous run across all endpoints. If that causes resource contention downstream, use [Job: Schedule Only](https://claude.ai/chat/job-schedule-only) instead and accept the delay to the first run.
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)
```

To confirm the Startup behavior specifically, restart the agent service and check that a run is recorded immediately:

```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-and-startup.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.
