Monitors
Follow specific jobs and get a job.closed webhook when one closes. A monitor lists the jobs, names one of your webhook endpoints, and runs for 1 to 365 days (default 90). Free.
A monitor is a list of jobs you want to follow. When one closes, JobsPipe sends job.closed to one of your webhook endpoints, so you don't have to re-fetch jobs to find out.
Monitors are available from the Builder package up and cost no credits, including every event they send.
| Limit | Builder, Growth | Scale, Business |
|---|---|---|
| Records monitored, all monitors | 100,000 | 1,000,000 |
| Records in one monitor | 100,000 | 100,000 |
| Ids per request | 1,000 | 1,000 |
| Lifetime | 1 to 365 days, default 90, renewable | same |
All endpoints accept an API key (Authorization: Bearer jp_live_... or x-api-key) or an OAuth access token.
Create a monitor
POST https://api.jobspipe.dev/v1/monitorscurl https://api.jobspipe.dev/v1/monitors \
-H "Authorization: Bearer $JOBSPIPE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "job",
"ids": ["4464825573", "8739185002", "no-such-job"],
"webhook_id": "0f8b6c1e-3a52-4d7e-9c41-2b8f7a6d5e30",
"expires_in_days": 30
}'| Field | Required | Description |
|---|---|---|
type | yes | What the ids are. job today. |
ids | yes | 1 to 1,000 ids, as search returned them. |
webhook_id | yes | One of your webhook endpoints (GET /api/webhooks). |
events | no | Events to send. Default: every event of the type, which for jobs is job.closed. |
expires_in_days | no | How long the monitor runs, a whole number from 1 to 365. Default 90. |
external_id | no | Your own key for the monitor, unique among your monitors: up to 200 letters, digits and . _ : @ -. |
With external_id, a monitor is only created if you have none with that key. If one exists, the
answer is 409 with its id ({"error": "...", "id": "7f3c..."}) and nothing is created. To create
or update by key instead, use PUT below.
The response also lists which jobs are already closed and which ids match nothing. Neither is added: only open jobs are monitored.
{
"id": "7f3c1a52-8a3e-4c1e-9f0a-2d6b4e1c9a77",
"type": "job",
"events": ["job.closed"],
"webhook_id": "0f8b6c1e-3a52-4d7e-9c41-2b8f7a6d5e30",
"external_id": null,
"status": "active",
"expires_at": "2026-10-24T10:00:00.000Z",
"created_at": "2026-09-24T10:00:00.000Z",
"target_count": 1,
"open_target_count": 1,
"already_closed": [
{ "id": "8739185002", "closed_at": "2026-09-20T00:00:00Z", "closed_reason": "gone" }
],
"not_found": ["no-such-job"]
}| Field | Description |
|---|---|
status | active until expires_at, then expired. An expired monitor sends nothing until renewed. |
target_count | Records in the monitor. |
open_target_count | Records whose close has not been sent yet. |
Create or update by your own key
PUT https://api.jobspipe.dev/v1/monitors/by-external-id/{external_id}curl -X PUT https://api.jobspipe.dev/v1/monitors/by-external-id/crm-account-0042 \
-H "Authorization: Bearer $JOBSPIPE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "job",
"ids": ["4464825573", "5211351007"],
"webhook_id": "0f8b6c1e-3a52-4d7e-9c41-2b8f7a6d5e30",
"expires_in_days": 30
}'The body is the same as for create. If you have no monitor with this external_id, one is created
(201, "created": true). Otherwise that monitor is updated (200, "created": false):
events,webhook_idand the expiry are replaced. The expiry becomesexpires_in_daysfrom now (default 90), so sending the same body again also renews the monitor.- The records become exactly
ids: open ids not yet in the monitor are added, records not inidsare removed, and records already in it stay as they are (a record whosejob.closedwas sent staysclosed). Already-closed and unknown ids are reported, not added. typecannot change (400).
Since ids holds up to 1,000 ids, a monitor kept by PUT holds up to 1,000 records. For a larger
one, use the add and remove endpoints below.
{
"id": "7f3c1a52-8a3e-4c1e-9f0a-2d6b4e1c9a77",
"type": "job",
"events": ["job.closed"],
"webhook_id": "0f8b6c1e-3a52-4d7e-9c41-2b8f7a6d5e30",
"external_id": "crm-account-0042",
"status": "active",
"expires_at": "2026-11-24T10:00:00.000Z",
"created_at": "2026-10-01T10:00:00.000Z",
"target_count": 2,
"open_target_count": 2,
"created": false,
"added": 1,
"removed": 2,
"already_closed": [],
"not_found": []
}Try a change first: dry_run
PUT /v1/monitors/by-external-id/{external_id}, DELETE /v1/monitors/{id} and
DELETE /v1/monitors/{id}/ids take ?dry_run=true. Every check and id lookup runs (an error is the
same error you would get), but nothing changes, and the answer says what would happen:
| Request | Dry-run answer |
|---|---|
PUT .../by-external-id/{external_id}?dry_run=true | {"dry_run": true, "created", "id", "would_add", "would_remove", "target_count", "already_closed", "not_found"}. id is null when it would create. |
DELETE /v1/monitors/{id}?dry_run=true | {"dry_run": true, "id", "would_delete": true, "target_count", "restorable_until"} |
DELETE /v1/monitors/{id}/ids?dry_run=true | {"dry_run": true, "would_remove"} |
Add and remove records
POST https://api.jobspipe.dev/v1/monitors/{id}/ids {"ids": [...]}
DELETE https://api.jobspipe.dev/v1/monitors/{id}/ids {"ids": [...]}
GET https://api.jobspipe.dev/v1/monitors/{id}/ids?limit=100&cursor=...Adding answers {"added", "target_count", "already_closed", "not_found"}; ids already in the
monitor are ignored. Removing answers {"removed"}. The list shows each record with status
open or closed: a record whose job.closed was sent stays listed as closed and sends nothing
more.
Renew, list, get and delete
POST https://api.jobspipe.dev/v1/monitors/{id}/renew {"expires_in_days": 90}
GET https://api.jobspipe.dev/v1/monitors?limit=100&cursor=...
GET https://api.jobspipe.dev/v1/monitors/{id}
DELETE https://api.jobspipe.dev/v1/monitors/{id}Renewing sets expires_at to expires_in_days (default 90) from now, and works on an expired
monitor too. The list is newest first; pass next_cursor back as cursor until it is null.
Deleting a webhook endpoint deletes the monitors that deliver to it, and those cannot be restored.
Delete and restore
Deleting a monitor stops it at once: it sends nothing, is left out of the list, answers 404, and no
longer counts toward your monitored-records limit. It can be restored within 30 days:
DELETE https://api.jobspipe.dev/v1/monitors/{id}
POST https://api.jobspipe.dev/v1/monitors/{id}/restore{ "id": "7f3c1a52-8a3e-4c1e-9f0a-2d6b4e1c9a77", "deleted": true, "restorable_until": "2026-11-24T10:00:00.000Z" }Restoring brings back the monitor as it was: its records, external_id and expires_at (an expiry
that passed while it was deleted shows it as expired; renew it). Events that happened while it was
deleted are not sent afterwards: a job that closed in that time stays listed as open in the
monitor and never sends job.closed, so check those records with a search if it matters. After restorable_until the
restore answers 404. Restoring a monitor that is not deleted returns it unchanged. A deleted
monitor's external_id is free for a new monitor; restoring the old one then answers 409 with the
new monitor's id. A restore that would pass your monitored-records limit answers 402.
What you receive
When a record in an active monitor closes, the monitor's endpoint receives a signed
job.closed event whose monitor_ids names the monitors
that include the job. An endpoint that reaches the same job through two monitors receives it once.
Only verified closes are sent (gone, closed), and the endpoint must list job.closed in its
enabledEvents.
Errors
| Status | When |
|---|---|
400 | Unknown type or event, ids empty or over 1,000, expires_in_days not a whole number from 1 to 365, a monitor would pass 100,000 records, an external_id that is not 1 to 200 of the allowed characters, or a PUT that changes type. |
401 | No valid credential. |
402 | Free plan, or the request would pass the plan's monitored-records limit. |
404 | The monitor or webhook does not exist on your account, or a restore is past its 30 days. |
409 | Another of your monitors already has this external_id; the answer's id names it. |