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:fieldsholds the served fields that moved with their new values; set them. No fetch needed.closed: drop or hide the job;closed_atsays 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.