# Create a scheduled trigger (agent-cronjob)

You are an agent. This page is enough to have this service POST to your webhook on a schedule —
signed, retried and logged. No account, no API key.
Complete reference: https://cronjob.agentasy.ai/llms.txt · OpenAPI 3.1: https://cronjob.agentasy.ai/openapi.json

## 1. Create the job

POST https://new-cronjob.agentasy.ai/  (same as POST https://cronjob.agentasy.ai/v1/jobs), Content-Type: application/json

```json
{
  "webhook_url": "https://your-agent.example/hooks/abc",
  "schedule": { "cron": "*/15 * * * *", "timezone": "Asia/Taipei" },
  "payload": { "task": "daily-digest" },
  "max_runs": null,
  "catchup_policy": "run_once",
  "verification_mode": "echo"
}
```

- `schedule`: exactly one of `{"cron": "<5 fields>", "timezone": "<IANA>"}`,
  `{"interval_sec": 3600}` or `{"run_at": "<ISO 8601 with Z or offset, future>"}`. Minimum spacing 60 s.
  The cron timezone defaults to **UTC**; send `timezone` for anything else.
- `webhook_url` must be https on a public address (private, loopback, link-local and metadata
  addresses are refused; redirects are never followed).
- Optional: `name`, `max_runs` (job completes after N runs; raise it later with `PATCH`), `catchup_policy`
  (`skip` | `run_once` | `run_all`), `catchup_window_sec`, `retry_max_attempts`,
  `retry_base_sec`, `retry_max_backoff_sec`, `timeout_ms`. Unknown fields are rejected (400).
- Optional `outgoing_auth`: `{"header": "Authorization", "value": "Bearer <token>"}` if your webhook
  itself requires a header (e.g. a routine webhook). Sent on the challenge and every trigger;
  stored encrypted, never shown back. `header` defaults to `Authorization` (or any `X-…` name).
- Optional `signature_in_body`: `true` if your platform drops our `X-Cronjob-*` headers before your
  agent sees the request (e.g. **Grok Bot routines**). Every trigger body then also carries a
  `cronjob_signature` object you can verify from the JSON alone (https://cronjob.agentasy.ai/llms.txt#body-signature).
  Default `false`; change it later with `PATCH https://cronjob.agentasy.ai/v1/jobs/{job_id}`.

Response 202: `job_id`, `setup_token` (keep it; shown once) and `verification` links.

## 2. Prove you control the webhook (deadline 1: 15 minutes after creation)

We POST to your webhook (header `X-Cronjob-Event: url_verification`):

```json
{ "type": "url_verification", "job_id": "job_...", "challenge": "<random>", "verify_url": "https://cronjob.agentasy.ai/v1/jobs/job_.../verify" }
```

Complete it either way — both always work, whatever `verification_mode` you sent (that field is
informational):

- **echo**: answer 2xx with body `{"challenge": "<same value>"}` (`Content-Type: application/json`)
  or the bare value as `text/plain`.
- **callback**: answer 2xx, then `POST <verify_url>` with `{"challenge": "<same value>"}`.
  The response contains your credentials (step 3) — store them.

Not received? `POST https://cronjob.agentasy.ai/v1/jobs/{job_id}/verification/retry` with
`Authorization: Bearer <setup_token>` (at most 5 re-sends (6 challenges in total), at least 60 s apart).
Deadline 1 (verify): 15 minutes after job creation — unverified jobs are deleted; re-sends do not extend it.

## 3. Collect your credentials (once)

`GET https://cronjob.agentasy.ai/v1/jobs/{job_id}/verification` with `Authorization: Bearer <setup_token>`.
After verification the first call returns `hmac_secret` (acs_...) and `management_token`
(act_mt_...). They are shown exactly once. Deadline 2 (collect credentials): 15 minutes after verification — uncollected jobs are deleted. Collect them right away: triggers start right after verification, and until your receiver
has the secret answer triggers with `503` (retried), not `401` (final).

## 4. Receive triggers

```
POST <webhook_url>
X-Cronjob-Job-Id, X-Cronjob-Delivery-Id (same on every retry), X-Cronjob-Attempt,
X-Cronjob-Scheduled-At, X-Cronjob-Timestamp (unix seconds), X-Cronjob-Signature: v1=<hex>[, v1=<hex>]
{ "type": "trigger", "job_id", "delivery_id", "attempt", "scheduled_at", "sent_at", "run_number", "payload" }
```

Verify: `hex(HMAC-SHA256(key = hmac_secret as UTF-8, "v1:" + X-Cronjob-Timestamp + ":" + X-Cronjob-Delivery-Id + ":" + raw_body))`
equals any `v1=` value. Reject if the timestamp is more than 5 minutes off; de-duplicate on delivery_id.
Copy-paste Node and Python verifiers: https://cronjob.agentasy.ai/llms.txt#signature
With `signature_in_body: true` the body also has `"cronjob_signature": {"version": "v1", "timestamp", "delivery_id",
"signatures": [...], "signature"}`: verify that instead when the headers do not reach you (https://cronjob.agentasy.ai/llms.txt#body-signature).
Reply 2xx to acknowledge. 5xx/408/429/timeouts are retried with exponential backoff; other 4xx are
final; 410 pauses the job.

## 5. Manage the job (Authorization: Bearer <management_token>)

| Method | Path | |
|---|---|---|
| GET | https://cronjob.agentasy.ai/v1/jobs/{job_id} | settings and status |
| PATCH | https://cronjob.agentasy.ai/v1/jobs/{job_id} | `{"signature_in_body": true}` and/or `{"max_runs": 20}`; raising `max_runs` reactivates a job completed by `max_runs_reached` |
| POST | https://cronjob.agentasy.ai/v1/jobs/{job_id}/pause · /resume | |
| DELETE | https://cronjob.agentasy.ai/v1/jobs/{job_id} | stop forever, revoke credentials |
| POST | https://cronjob.agentasy.ai/v1/jobs/{job_id}/rotate-secret | `{"grace_sec": 86400}` old secret keeps co-signing |
| POST | https://cronjob.agentasy.ai/v1/jobs/{job_id}/rotate-token | `{"grace_sec": 0}` (≤ 3600) |
| GET | https://cronjob.agentasy.ai/v1/jobs/{job_id}/runs?since=&until=&status=&limit=&cursor= | trigger log |
| GET | https://cronjob.agentasy.ai/v1/jobs/{job_id}/runs/{run_id} | every attempt |
| GET | https://cronjob.agentasy.ai/v1/jobs/{job_id}/stats?window=24h | on-time rate, p50/p95 delay |

Errors are JSON `{"error": "<code>", "message": "..."}`; 429 responses carry Retry-After.

Terms: https://new-cronjob.agentasy.ai/terms · Pricing: https://new-cronjob.agentasy.ai/pricing (free during public beta)
