Job Monkey

Your first sync in three calls

A sync is three calls, and only one of them costs money.

Call What it does Charged
GET /v1/jobs (or a snapshot file) the first load of your cut, page by page in GUID order every full job, once per page
GET /v1/events what changed since your cursor: new, changed (with the new values), closed, reopened nothing
POST /v1/jobs the full record of the GUIDs you want after a new or reopened event every full job returned

1. The first load

Page your cut until more is false. Keep the last page's ledger_seq: that is your first cursor.

curl -s 'https://api.jobmonkey.ai/v1/jobs?country=Canada&system=greenhouse,lever.co&limit=1000' \
  -H 'Authorization: Bearer cb_live_XXXXXXXX'

A job that changed, closed or was born while you were paging is harmless: the same change reaches you as an event from the cursor, and every event is idempotent (set the fields, upsert, drop). Held closures are in the pages, marked by closing_since: keep them out of matching while it is set.

For a whole country at once, a snapshot file is the same jobs in one download; the price is the same per job.

2. Read events from the cursor

curl -s 'https://api.jobmonkey.ai/v1/events?country=Canada&system=greenhouse,lever.co&cursor=9648658&limit=10000' \
  -H 'Authorization: Bearer cb_live_XXXXXXXX'

Apply each event in seq order:

  • new: fetch the job by GUID (step 3) and insert it.
  • changed: fields holds the served fields that moved with their new values; set them. No fetch needed.
  • closed: drop or hide the job; closed_at says when it left its board.
  • reopened: fetch it again by GUID.

Pass next back as cursor; when more is false you are at top. Call as often as you like: events are free and have no daily cap on a paid key.

3. Fetch the full records

curl -s -X POST https://api.jobmonkey.ai/v1/jobs \
  -H 'Authorization: Bearer cb_live_XXXXXXXX' -H 'Content-Type: application/json' \
  -d '{"guids": ["21b3928d-c2ea-4b3b-8281-7bdf05adbf1b"]}'

Up to 1,000 GUIDs a call. A GUID that closed between the event and your call comes back in missing, free. Send an Idempotency-Key header so a retry after a timeout costs nothing.

The loop in Python

import json, urllib.request

KEY, API = "cb_live_XXXXXXXX", "https://api.jobmonkey.ai"
CUT = "country=Canada&system=greenhouse,lever.co"

def call(path, body=None):
    data = json.dumps(body).encode() if body else None
    req = urllib.request.Request(API + path, data=data, headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"})
    with urllib.request.urlopen(req) as r:
        return json.load(r)

# 1. the first load
cursor, page = None, {"more": True}
while page["more"]:
    page = call(f"/v1/jobs?{CUT}&limit=1000" + (f"&cursor={page['next']}" if page.get("next") else ""))
    store(page["jobs"])                      # your upsert
    cursor = page["ledger_seq"]

# 2 and 3. every few minutes, for ever
while True:
    ev = call(f"/v1/events?{CUT}&cursor={cursor}&limit=10000")
    want = [e["job_guid"] for e in ev["events"] if e["event"] in ("new", "reopened")]
    for i in range(0, len(want), 1000):
        store(call("/v1/jobs", {"guids": want[i:i + 1000]})["jobs"])
    for e in ev["events"]:
        if e["event"] == "changed": update(e["job_guid"], e["fields"])
        elif e["event"] == "closed": drop(e["job_guid"])
    cursor = ev["next"]
    if not ev["more"]: sleep(300)

Store the cursor with your data, in the same transaction. If you lose it, GET /v1/events?since=<time> finds the first event at or after a time, and your account page shows the last cursor your key sent.