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.

LimitBuilder, GrowthScale, Business
Records monitored, all monitors100,0001,000,000
Records in one monitor100,000100,000
Ids per request1,0001,000
Lifetime1 to 365 days, default 90, renewablesame

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/monitors
curl 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
  }'
FieldRequiredDescription
typeyesWhat the ids are. job today.
idsyes1 to 1,000 ids, as search returned them.
webhook_idyesOne of your webhook endpoints (GET /api/webhooks).
eventsnoEvents to send. Default: every event of the type, which for jobs is job.closed.
expires_in_daysnoHow long the monitor runs, a whole number from 1 to 365. Default 90.
external_idnoYour 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.

201 Created
{
  "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"]
}
FieldDescription
statusactive until expires_at, then expired. An expired monitor sends nothing until renewed.
target_countRecords in the monitor.
open_target_countRecords 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_id and the expiry are replaced. The expiry becomes expires_in_days from 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 in ids are removed, and records already in it stay as they are (a record whose job.closed was sent stays closed). Already-closed and unknown ids are reported, not added.
  • type cannot 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.

200 OK
{
  "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:

RequestDry-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
DELETE 200 OK
{ "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

StatusWhen
400Unknown 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.
401No valid credential.
402Free plan, or the request would pass the plan's monitored-records limit.
404The monitor or webhook does not exist on your account, or a restore is past its 30 days.
409Another of your monitors already has this external_id; the answer's id names it.

On this page