Greenhouse, Lever & Ashby Jobs Scraper + Change Monitor
Pricing
$30.00 / 1,000 successful board checks
Greenhouse, Lever & Ashby Jobs Scraper + Change Monitor
Scrape company job boards to JSON or CSV, then track new listings, edits and confirmed removals. Exact before/after values and persistent history help keep job databases current. Includes an n8n/Google Sheets starter. $0.03 per successful board check, with platform usage included.
Pricing
$30.00 / 1,000 successful board checks
Rating
0.0
(0)
Developer
Apter Enterprises
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
9 days ago
Last modified
Categories
Security
Limited permissionsShare
Keep a job database current with public Greenhouse, Lever and Ashby postings. The first scan exports an inventory; later scans return newly observed listings, exact edits and confirmed removals. Export JSON or CSV and retain before/after values for title, location, reported salary, workplace type and description changes.
Designed for recruiting-data workflows, job-board maintenance and company watchlists. Uses the providers' public APIs. No employer login, candidate data, proxy configuration or external AI key is needed.
Try it with three companies
Open the Sentry, PostHog & Clerk example to start with three company boards already filled in. Choose Try for free on that page to copy the example into your Apify account, review the inputs, then start your run. No spreadsheet or n8n setup is needed.
Three successful board checks cost $0.09 in total, including platform usage. First and unchanged checks count; available Apify credits may cover the charge.
Your first scan exports current jobs as baseline records plus a board_checked receipt for each successful board. Export the dataset as JSON or CSV. Reuse the same monitor name on later runs to see changes; if nothing changed, the receipts still confirm the checks completed.
Quick start with your own boards
- Paste the employer's public Greenhouse, Lever or Ashby board URL. Existing
provider:board-slugidentifiers also work. - Choose a monitor name and run the Actor. The first successful scan saves a baseline.
- Reuse that monitor name on later runs. The output then contains newly observed listings and changes.
- Save an Apify task and schedule it daily if you want ongoing monitoring.
{"boards": ["https://job-boards.greenhouse.io/stripe", "https://jobs.ashbyhq.com/ramp"],"monitorName": "company-watchlist","includeBaseline": true,"includeCheckRecords": true}
Supported URL hosts are boards.greenhouse.io, job-boards.greenhouse.io, jobs.lever.co, jobs.eu.lever.co and jobs.ashbyhq.com. A supported job-detail link selects its whole company board, not only that job. Custom careers domains and Greenhouse embed links are not supported. Tracking query parameters are ignored. Identifiers such as greenhouse:stripe, lever:company, lever-eu:company and ashby:ramp remain supported. Switching between a URL and its matching identifier preserves history with the same monitor name. Do not include both versions in one run.
This Actor monitors boards you supply; it does not discover employers or search a global jobs database. The optional n8n starter below still uses provider:board-slug identifiers in its configuration.
Keep an existing job board current
Use (provider, board, job_id) as the source record key. On the first run, import baseline records. On subsequent runs, upsert newly_observed, changed and reappeared records using their job object; mark a record inactive only after no_longer_listed. Deduplicate deliveries with event_id and keep the same monitor name. Never interpret an empty changes dataset as an empty company board.
Save your input as an Apify task and schedule it daily after verifying a manual run. A failed source check preserves its previous history and cannot confirm removals. At $0.03 per successful board check, 10 company boards checked daily for 30 days cost $9; first and unchanged checks count, with platform usage included. Any external database or integration service costs are separate.
Recruiter watchlist starter
Follow the companies your recruiting desk cares about. Use the free n8n starter below to deliver backend, platform, infrastructure, DevOps and SRE listing changes to Google Sheets. Every matching event keeps a source URL and observation date. A separate Runs tab shows successful checks, failures and incomplete exports.
A live demonstration on October 8, 2026 (Eastern time) checked 25 public company boards containing 3,077 listing records. Its title filter selected 158 initial records across 21 companies. These were an initial inventory, not 158 newly posted jobs. A second scan succeeded on all 25 boards and found one legal-role description change, correctly excluded by the engineering filter; there were zero matching changes during that short interval.
Examples from that dated initial inventory; live listings may have changed:
| Company | Public posting | Source location |
|---|---|---|
| Ramp | Software Engineer, Data Platform | New York, NY (HQ) |
| Vanta | Backend Senior Software Engineer, Identity | Remote U.S. |
| Figma | Data Platform Engineer | San Francisco, CA • New York, NY • United States |
| Modal | Member of Technical Staff - Platform Engineering | New York |
| Render | Software Engineer, Infrastructure (all levels) | Remote: Canada |
This watchlist covers global locations, not only US roles. Job IDs are source records and may include overlapping requisitions or multiple locations. Relevant jobs with generic titles can be missed. A listing does not prove the employer wants an agency or has a new hiring budget.
Cost and setup
The starter workflow has no separate license charge. At the current $0.03 per successful board check, 25 companies checked daily cost $22.50 over 30 days in JobDelta usage, including platform usage for this Actor. Your n8n plan, taxes and other services are separate. First and unchanged successful scans count.
- Create a native Google Sheets spreadsheet with tabs named
SignalsandRuns. Paste the two header lines below into cell A1 of their respective tabs. If the paste stays in one cell, use Data → Split text to columns → Comma. Keep the exact column names and order. - Copy the JSON from Importable n8n workflow below into a
.jsonfile and import it into n8n. The workflow uses only built-in nodes and is inactive on import. - Edit Configure watchlist: set your spreadsheet ID, choose 1–25 supported board identifiers, and keep a stable
monitorName. Replace the illustrative company list with your actual target accounts. The maximum run charge defaults to $0.75. - Select your Google Sheets OAuth credential in Check spreadsheet headers and the three Sheets nodes. Your Google account needs edit access to the spreadsheet.
- In all five Apify HTTP nodes, select an HTTP Header Auth credential with header name
Authorizationand valueBearer YOUR_APIFY_TOKEN. Enter your own token in n8n's credential screen; never put it in a URL, spreadsheet or shared workflow JSON. - Run manually and check
Runs.COMPLETEmeans the export succeeded. By default, the first inventory is suppressed fromSignals; setincludeBaselinetotruebefore the first run if you want it. The Actor always stores baseline events for later replay. - After verifying your integration, activate the workflow for daily checks at 08:00 America/New_York. Keep one workflow writer per sheet and avoid concurrent manual runs. Do not also schedule the same monitor separately in Apify.
Signals headers
event_id,run_id,observed_at,signal,company,board,title,locations,source_url,first_seen_at,source_published_at,changed_fields,change_note,notice
Runs headers
run_id,status,started_at,finished_at,successful_boards,failed_or_skipped_boards,charged_board_checks,dataset_item_count,matched_signals,export_status,notes,run_url
Filtering and recovery
Title inclusion/exclusion keywords use case-insensitive word boundaries; a title must also contain engineer, engineering, developer or architect. Optional location keywords match literal text. Empty location keywords mean all locations. Edits that move a role out of scope are retained if the previous title/location matched. Spreadsheet change text is excerpted; full before/after values stay in the saved Apify dataset while retained.
The workflow validates spreadsheet headers before a billable scan, paginates the dataset and checks its final item count. It upserts job events by event_id and scan receipts by run_id. Zero matching signals still create a Runs receipt. A partial source failure saves available matches, marks COMPLETE_WITH_SOURCE_ISSUES, then flags the n8n execution for attention. PENDING means spreadsheet export has not completed.
If export fails, replay the saved run. Pause the schedule, find the Apify run ID in the n8n execution or Apify console, put it in replayRunId, and rerun. This reads the saved dataset without starting another board scan. Clear the ID after recovery. If the run-start request times out, check Apify before retrying: a run might already exist. Monitoring state can advance before Sheets writes finish, so starting a new scan is not a substitute for replaying the missing export.
Keep headers and stable IDs intact. Serial upserts are not an atomic lock against concurrent writers. Recover before your account's run-data retention expires. Filter changes do not automatically reclassify older spreadsheet rows. The workflow caps exports at 100,000 records; the Actor's board and state-size limits still apply. Split a large watchlist if its state reaches the 32 MiB limit.
Validation status: 22 local checks passed for filtering, pagination counts, source failures and recovery configuration. Both 25-board source scans succeeded. The complete n8n/Google OAuth/Sheets path has not yet been verified against a customer account; treat this as an integration starter and test a manual execution before scheduling.
References: n8n workflow import, Google Sheets operations, HTTP Request pagination, Apify run status, dataset export.
Outputs
The default dataset contains observed job events and, when includeCheckRecords is enabled, one board_checked receipt per successful scan. JSON exports preserve the complete nested before/after values. SUMMARY in the run's key-value store gives board counts, errors, pending absences and charged checks. EVENTS contains only job events and never includes check receipts.
| Event | Interpretation |
|---|---|
baseline | Present at the first scan. These are not necessarily newly posted jobs. |
newly_observed | A job ID first seen after a baseline exists. |
changed | One or more monitored fields changed; changes contains old and new values. |
no_longer_listed | Repeated successful scans confirmed that a listing is absent. The hiring outcome is unknown. |
reappeared | A previously confirmed missing job ID is listed again. |
board_checked | Optional scan receipt with listing and job-event counts. This is not a job change or payment receipt. |
The example form enables check receipts so an unchanged scan still has useful output. Receipts have job_id: null, listed_jobs and job_events; their source_url is the scanned public API endpoint. Set includeCheckRecords to false for a job-events-only dataset, which can be empty when nothing changed. API calls that omit this option default to false. Failed or budget-skipped boards do not receive successful-check receipts; inspect SUMMARY for their errors. The local CLI always exports job events only.
Illustrative change record — synthetic values, not a real employer event:
{"event_type": "changed","provider": "ashby","board": "synthetic-example","title": "Senior Software Engineer","changed_fields": ["title", "workplace_type"],"changes": {"title": {"before": "Software Engineer", "after": "Senior Software Engineer"},"workplace_type": {"before": "Hybrid", "after": "Remote"}}}
Each real job-event record also includes event_id, job_id, observed_at, previous_observed_at, first_seen_at, source_url, source_endpoint and a current job summary.
Reported salary, not estimated salary
Lever and Ashby structured compensation retains the source's currencies, periods and tiers. Missing compensation stays null. Greenhouse does not provide a universal structured salary field through this endpoint, so its salary text is covered by description changes, not extracted salary amounts. Ashby postings or compensation explicitly hidden from public display are excluded.
History and missing listings
The same monitor name reuses a named state store in your Apify account. Different names create independent monitors. An optional state-store picker lets you reuse an existing JobDelta state store. The Actor uses limited permissions and a queue lock to serialize runs sharing the same monitor.
By default, a listing must be absent from two successful checks spanning at least six hours before no_longer_listed is emitted. If more than half of a board with at least ten active listings disappears, the requirements increase to at least three checks spanning 24 hours. A failed fetch or rejected partial response never advances that board's history.
These safeguards reduce false disappearance alerts; they cannot prove that an upstream API returned every available job. A job disappearing does not establish that it was filled, canceled or removed permanently.
Billing unit
The unit is one successfully checked company board, at the price shown on the Store page. The first baseline scan counts. An unchanged successful scan counts. A failed source check does not produce a board-check charge. Job and change counts do not add separate event charges. Set the run's maximum charge to cap paid checks; boards beyond it are skipped and reported in SUMMARY.
Successful checks in an otherwise partially failed run may be charged and their results remain available. Inspect the run status and SUMMARY before automatically retrying a mixed-success batch.
Limits and interpretation
- Up to 25 boards per run, 3,000 jobs per board by default, configurable up to 5,000. Larger watchlists can use multiple monitors.
- Size limits are 20 MiB per response, 32 MiB received per board, 100,000 characters per description and 32 MiB of monitor state. Exceeding them fails that check instead of silently truncating it.
- Whitespace, HTML presentation, location order and compensation component IDs are normalized. Formatting-only edits are not events.
- Source job IDs define identity. A repost under a new ID is a newly observed listing. No cross-board deduplication or hiring-intent scoring is applied.
- Observation times reflect this monitor's scans. Source publication dates are preserved separately and may represent republication.
- Confirmed missing listings remain in history for 90 days. Reappearance after that window is treated as newly observed.
- A crash can leave partial output or replay events on retry. Deduplicate by
event_id. Deleting the named state store resets your baseline. - No historic changes are available from before your first scan. This Actor does not send email, apply for jobs or access private applicant records.
Independent tool using the Greenhouse, Lever and Ashby public job-board APIs. No affiliation with the providers or monitored employers is implied.
Importable n8n workflow
Workflow revision: recruiter-starter-1. Copy the entire JSON code block into jobdelta-recruiter-n8n.json, import it in n8n, then follow the setup instructions above. Credentials are intentionally not embedded. This is an inactive 27-node workflow, not a running service.
{"name": "JobDelta — Backend and Infrastructure Hiring Watchlist","nodes": [{"parameters": {"content": "## JobDelta → recruiter watchlist\n1. Import the blank workbook into Google Sheets. Keep Signals / Runs headers.\n2. Configure the sheet ID and watchlist.\n3. Select Google Sheets credentials in the preflight and three Sheets nodes.\n4. Select an HTTP Header Auth credential (Authorization: Bearer YOUR_APIFY_TOKEN) in every Apify HTTP node. Never paste a token in the URL or code.\n5. Run manually; inspect Runs and Signals. Schedule is inactive until you publish/activate this workflow. Daily at 08:00 America/New_York.\n\n$0.03 per successful board check. Default 25 boards = up to $0.75/run and $22.50/30 daily runs, plus your n8n plan.\n\nInitial inventory is suppressed by default. Set includeBaseline=true BEFORE the first run if wanted. Every board is still baselined.\n\nIf export fails, copy its Apify run ID into replayRunId and rerun. This reads the saved run without starting/billing another scan. Clear replayRunId afterward. Keep one writer per sheet; never run overlapping executions. Read the setup guide for recovery and retention limits.","height": 690,"width": 620},"id": "d4a4b7cb-0433-5e50-8509-7b1f5bd90a00","name": "Read me first","type": "n8n-nodes-base.stickyNote","typeVersion": 1,"position": [-740,-460]},{"parameters": {},"id": "5a3e29fd-1fe2-54bd-8d82-348e9a4b35a7","name": "Run manually","type": "n8n-nodes-base.manualTrigger","typeVersion": 1,"position": [0,0]},{"parameters": {"rule": {"interval": [{"field": "days","daysInterval": 1,"triggerAtHour": 8,"triggerAtMinute": 0}]}},"id": "067d9ae9-17f3-5c4f-ac29-c4c033915f75","name": "Daily at 8am - inactive until enabled","type": "n8n-nodes-base.scheduleTrigger","typeVersion": 1.2,"position": [0,180]},{"parameters": {"jsCode": "const config = {\n \"spreadsheetId\": \"PASTE_YOUR_GOOGLE_SPREADSHEET_ID\",\n \"monitorName\": \"recruiter-backend-v1\",\n \"boards\": [\n \"ashby:ramp\",\n \"ashby:vanta\",\n \"ashby:linear\",\n \"ashby:notion\",\n \"greenhouse:figma\",\n \"greenhouse:anthropic\",\n \"greenhouse:brex\",\n \"greenhouse:mercury\",\n \"ashby:modal\",\n \"ashby:supabase\",\n \"ashby:render\",\n \"greenhouse:sourcegraph91\",\n \"ashby:ashby\",\n \"ashby:coder\",\n \"ashby:dandy\",\n \"ashby:middesk\",\n \"ashby:docker\",\n \"ashby:clerk\",\n \"ashby:posthog\",\n \"ashby:sentry\",\n \"greenhouse:stripe\",\n \"ashby:zapier\",\n \"ashby:benchling\",\n \"ashby:elevenlabs\",\n \"ashby:cursor\"\n ],\n \"companyNames\": {\n \"ashby:ramp\": \"Ramp\",\n \"ashby:vanta\": \"Vanta\",\n \"ashby:linear\": \"Linear\",\n \"ashby:notion\": \"Notion\",\n \"greenhouse:figma\": \"Figma\",\n \"greenhouse:anthropic\": \"Anthropic\",\n \"greenhouse:brex\": \"Brex\",\n \"greenhouse:mercury\": \"Mercury\",\n \"ashby:modal\": \"Modal\",\n \"ashby:supabase\": \"Supabase\",\n \"ashby:render\": \"Render\",\n \"greenhouse:sourcegraph91\": \"Sourcegraph\",\n \"ashby:ashby\": \"Ashby\",\n \"ashby:coder\": \"Coder\",\n \"ashby:dandy\": \"Dandy\",\n \"ashby:middesk\": \"Middesk\",\n \"ashby:docker\": \"Docker\",\n \"ashby:clerk\": \"Clerk\",\n \"ashby:posthog\": \"PostHog\",\n \"ashby:sentry\": \"Sentry\",\n \"greenhouse:stripe\": \"Stripe\",\n \"ashby:zapier\": \"Zapier\",\n \"ashby:benchling\": \"Benchling\",\n \"ashby:elevenlabs\": \"ElevenLabs\",\n \"ashby:cursor\": \"Cursor\"\n },\n \"titleKeywords\": [\n \"backend\",\n \"back-end\",\n \"platform\",\n \"infrastructure\",\n \"site reliability\",\n \"devops\",\n \"sre\",\n \"distributed systems\"\n ],\n \"excludeTitleKeywords\": [\n \"recruit\",\n \"sales\",\n \"solutions engineer\",\n \"solution engineer\",\n \"customer\",\n \"technical program\",\n \"technical writer\",\n \"marketing\",\n \"designer\",\n \"intern\",\n \"internship\"\n ],\n \"locationKeywords\": [],\n \"includeBaseline\": false,\n \"maxTotalChargeUsd\": 0.75,\n \"replayRunId\": \"\"\n};\nif(!/^[A-Za-z0-9_-]{20,}$/.test(config.spreadsheetId) || config.spreadsheetId.startsWith('PASTE_')) throw new Error('Set spreadsheetId before starting a billable scan.');\nif(!/^[a-z0-9][a-z0-9-]{0,39}$/.test(config.monitorName)) throw new Error('Use a stable valid monitorName.');\nif(!Array.isArray(config.boards)||!config.boards.length||config.boards.length>25||new Set(config.boards).size!==config.boards.length) throw new Error('Use 1–25 unique board identifiers.');\nif(!config.boards.every(b=>/^(greenhouse|lever|lever-eu|ashby):[A-Za-z0-9][A-Za-z0-9_-]{0,79}$/.test(b))) throw new Error('Invalid board identifier.');\nif(config.replayRunId && !/^[A-Za-z0-9]{17}$/.test(config.replayRunId)) throw new Error('Invalid replayRunId.');\nif(typeof config.includeBaseline!=='boolean') throw new Error('includeBaseline must be true or false.');\nfor(const key of ['titleKeywords','excludeTitleKeywords','locationKeywords']) if(!Array.isArray(config[key])||config[key].some(x=>typeof x!=='string'||!x.trim()||x.length>100)) throw new Error('Use arrays of nonempty literal keywords.');\nif(!config.titleKeywords.length) throw new Error('Set at least one title keyword.');\nif(!Number.isFinite(config.maxTotalChargeUsd)||config.maxTotalChargeUsd<config.boards.length*0.03-1e-9||config.maxTotalChargeUsd>5) throw new Error('Budget must cover the watchlist and be at most $5/run.');\nconfig.executionStartedAt=Date.now();\nreturn [{json:config}];","mode": "runOnceForAllItems"},"id": "bfd295e7-953c-5e88-b55a-3a646d89f5ba","name": "Configure watchlist","type": "n8n-nodes-base.code","typeVersion": 2,"position": [240,0]},{"parameters": {"method": "GET","url": "={{ 'https://sheets.googleapis.com/v4/spreadsheets/' + $('Configure watchlist').first().json.spreadsheetId + '/values:batchGet?ranges=Signals%211%3A1&ranges=Runs%211%3A1' }}","authentication": "predefinedCredentialType","options": {"timeout": 90000,"redirect": {"redirect": {"followRedirects": false}},"response": {"response": {"responseFormat": "json","fullResponse": false,"neverError": false}}},"nodeCredentialType": "googleSheetsOAuth2Api"},"id": "ff06a189-2ecc-5072-bddd-d4b1e1908782","name": "Check spreadsheet headers","type": "n8n-nodes-base.httpRequest","typeVersion": 4.2,"position": [480,0],"retryOnFail": true,"maxTries": 3,"waitBetweenTries": 3000,"alwaysOutputData": false},{"parameters": {"jsCode": "const ranges=$input.first().json.valueRanges;\nconst expected=[[\"event_id\", \"run_id\", \"observed_at\", \"signal\", \"company\", \"board\", \"title\", \"locations\", \"source_url\", \"first_seen_at\", \"source_published_at\", \"changed_fields\", \"change_note\", \"notice\"], [\"run_id\", \"status\", \"started_at\", \"finished_at\", \"successful_boards\", \"failed_or_skipped_boards\", \"charged_board_checks\", \"dataset_item_count\", \"matched_signals\", \"export_status\", \"notes\", \"run_url\"]];\nif(!Array.isArray(ranges)||ranges.length!==2) throw new Error('Cannot read Signals and Runs tabs. Import the template workbook.');\nfor(let i=0;i<2;i++){ const actual=ranges[i].values?.[0]||[]; if(JSON.stringify(actual)!==JSON.stringify(expected[i])) throw new Error('The template header names/order changed. Restore the template before scanning.'); }\nreturn [{json:$('Configure watchlist').first().json}];","mode": "runOnceForAllItems"},"id": "244b7451-8431-5bd2-aadf-5af7bad113bb","name": "Validate sheet template","type": "n8n-nodes-base.code","typeVersion": 2,"position": [720,0]},{"parameters": {"conditions": {"options": {"caseSensitive": true,"leftValue": "","typeValidation": "strict","version": 2},"conditions": [{"id": "8146e8ad-7e09-415f-bd4f-3477ef38ba25","leftValue": "={{ Boolean($json.replayRunId) }}","rightValue": "","operator": {"type": "boolean","operation": "true","singleValue": true}}],"combinator": "and"},"options": {}},"id": "f521ebd0-4285-5d6c-b529-b20da2984009","name": "Replay existing run?","type": "n8n-nodes-base.if","typeVersion": 2.2,"position": [960,0]},{"parameters": {"jsCode": "return [{json:{data:{id:$('Configure watchlist').first().json.replayRunId}}}];","mode": "runOnceForAllItems"},"id": "c76173d9-a436-5f42-9a18-86b719f908b8","name": "Use saved run","type": "n8n-nodes-base.code","typeVersion": 2,"position": [1190,-100]},{"parameters": {"method": "POST","url": "https://api.apify.com/v2/acts/apterlabs~job-posting-change-monitor/runs","authentication": "genericCredentialType","options": {"timeout": 90000,"redirect": {"redirect": {"followRedirects": false}},"response": {"response": {"responseFormat": "json","fullResponse": false,"neverError": false}}},"genericAuthType": "httpHeaderAuth","sendQuery": true,"queryParameters": {"parameters": [{"name": "memory","value": "512"},{"name": "timeout","value": "600"},{"name": "maxTotalChargeUsd","value": "={{ $('Configure watchlist').first().json.maxTotalChargeUsd }}"}]},"sendBody": true,"specifyBody": "json","jsonBody": "={{ JSON.stringify({ boards: $('Configure watchlist').first().json.boards, monitorName: $('Configure watchlist').first().json.monitorName, includeBaseline: true, includeCheckRecords: true, maxJobsPerBoard: 3000, closureChecks: 2, closureMinHours: 6 }) }}"},"id": "8a2ade49-6bb4-5df6-8677-90128530cc8b","name": "Start JobDelta once","type": "n8n-nodes-base.httpRequest","typeVersion": 4.2,"position": [1190,140],"retryOnFail": false,"maxTries": 3,"waitBetweenTries": 3000,"alwaysOutputData": false},{"parameters": {"method": "GET","url": "={{ 'https://api.apify.com/v2/actor-runs/' + $json.data.id }}","authentication": "genericCredentialType","options": {"timeout": 90000,"redirect": {"redirect": {"followRedirects": false}},"response": {"response": {"responseFormat": "json","fullResponse": false,"neverError": false}}},"genericAuthType": "httpHeaderAuth","sendQuery": true,"queryParameters": {"parameters": [{"name": "waitForFinish","value": "60"}]}},"id": "8fae9c40-56de-5ee2-a954-22d49e6aa7bb","name": "Get run status","type": "n8n-nodes-base.httpRequest","typeVersion": 4.2,"position": [1440,0],"retryOnFail": true,"maxTries": 3,"waitBetweenTries": 3000,"alwaysOutputData": false},{"parameters": {"jsCode": "const r=$input.first().json.data;\nif(!r?.id || r.actId!=='JKg4A48Voq8wYW3Wh') throw new Error('The run does not belong to JobDelta.');\nconst terminal=['SUCCEEDED','FAILED','ABORTED','TIMED-OUT'];\nconst active=['READY','RUNNING','TIMING-OUT','ABORTING'];\nif(!terminal.includes(r.status)&&!active.includes(r.status)) throw new Error('Unknown Actor status: '+r.status);\nif(!terminal.includes(r.status)&&Date.now()-$('Configure watchlist').first().json.executionStartedAt>720000) throw new Error('Polling deadline exceeded. Check Apify run '+r.id+' and replay after it ends. Do not start a duplicate scan.');\nreturn [{json:{data:r,done:terminal.includes(r.status)}}];","mode": "runOnceForAllItems"},"id": "7318995d-c3d3-559b-bf0a-a871391c79c7","name": "Check run state","type": "n8n-nodes-base.code","typeVersion": 2,"position": [1680,0]},{"parameters": {"conditions": {"options": {"caseSensitive": true,"leftValue": "","typeValidation": "strict","version": 2},"conditions": [{"id": "19c327f9-ba09-408e-bc54-c573854d3e56","leftValue": "={{ $json.done }}","rightValue": "","operator": {"type": "boolean","operation": "true","singleValue": true}}],"combinator": "and"},"options": {}},"id": "9b7af65c-645f-50f5-921c-30b61aef8f24","name": "Run has ended?","type": "n8n-nodes-base.if","typeVersion": 2.2,"position": [1920,0]},{"parameters": {"resume": "timeInterval","amount": 15,"unit": "seconds"},"id": "232fbb18-c02e-5272-8471-780ffd9f7308","name": "Wait before polling","type": "n8n-nodes-base.wait","typeVersion": 1.1,"position": [1920,220]},{"parameters": {"jsCode": "return [{json:$input.first().json.data}];","mode": "runOnceForAllItems"},"id": "18fa43cc-b478-5ae8-9cff-885818656780","name": "Capture completed run","type": "n8n-nodes-base.code","typeVersion": 2,"position": [2100,-100]},{"parameters": {"method": "GET","url": "={{ 'https://api.apify.com/v2/key-value-stores/' + $('Capture completed run').first().json.defaultKeyValueStoreId + '/records/SUMMARY' }}","authentication": "genericCredentialType","options": {"timeout": 90000,"redirect": {"redirect": {"followRedirects": false}},"response": {"response": {"responseFormat": "json","fullResponse": true,"neverError": true}}},"genericAuthType": "httpHeaderAuth"},"id": "865e73e4-2362-527a-a8de-e2fc7a03ed18","name": "Get run summary","type": "n8n-nodes-base.httpRequest","typeVersion": 4.2,"position": [2160,0],"retryOnFail": true,"maxTries": 3,"waitBetweenTries": 3000,"alwaysOutputData": false},{"parameters": {"method": "GET","url": "={{ 'https://api.apify.com/v2/datasets/' + $('Capture completed run').first().json.defaultDatasetId }}","authentication": "genericCredentialType","options": {"timeout": 90000,"redirect": {"redirect": {"followRedirects": false}},"response": {"response": {"responseFormat": "json","fullResponse": false,"neverError": false}}},"genericAuthType": "httpHeaderAuth"},"id": "32092a9f-efee-59a8-b709-5e2585f299d4","name": "Get dataset metadata","type": "n8n-nodes-base.httpRequest","typeVersion": 4.2,"position": [2400,0],"retryOnFail": true,"maxTries": 3,"waitBetweenTries": 3000,"alwaysOutputData": false},{"parameters": {"jsCode": "const d=$input.first().json.data;\nif(!Number.isSafeInteger(d?.itemCount)||d.itemCount<0||d.itemCount>100000) throw new Error('Dataset exceeds this workflow limit or has invalid metadata; export the saved dataset manually. No further scan was started.');\nreturn [{json:d}];","mode": "runOnceForAllItems"},"id": "83ada245-dff4-5c66-95de-8234a5ad8462","name": "Check export size","type": "n8n-nodes-base.code","typeVersion": 2,"position": [2640,0]},{"parameters": {"method": "GET","url": "={{ 'https://api.apify.com/v2/datasets/' + $('Capture completed run').first().json.defaultDatasetId + '/items' }}","authentication": "genericCredentialType","options": {"timeout": 90000,"redirect": {"redirect": {"followRedirects": false}},"response": {"response": {"responseFormat": "json","fullResponse": false,"neverError": false}},"pagination": {"pagination": {"paginationMode": "updateAParameterInEachRequest","parameters": {"parameters": [{"type": "qs","name": "offset","value": "={{ $pageCount * 1000 }}"}]},"paginationCompleteWhen": "other","completeExpression": "={{ Array.isArray($response.body) && $response.body.length < 1000 }}","limitPagesFetched": true,"maxRequests": 100,"requestInterval": 100}}},"genericAuthType": "httpHeaderAuth","sendQuery": true,"queryParameters": {"parameters": [{"name": "format","value": "json"},{"name": "limit","value": "1000"},{"name": "clean","value": "false"},{"name": "fields","value": "event_id,event_type,observed_at,first_seen_at,provider,board,job_id,title,source_url,changed_fields,changes,job"}]}},"id": "8ac7946f-fc21-5109-9898-384ce2604f16","name": "Get all dataset pages","type": "n8n-nodes-base.httpRequest","typeVersion": 4.2,"position": [2880,0],"retryOnFail": true,"maxTries": 3,"waitBetweenTries": 3000,"alwaysOutputData": true},{"parameters": {"jsCode": "'use strict';\nconst LABELS = {\n baseline: 'Initial inventory', newly_observed: 'First observed after baseline',\n changed: 'Listing changed', no_longer_listed: 'No longer listed', reappeared: 'Listed again'\n};\nconst SIGNAL_COLUMNS = ['event_id','run_id','observed_at','signal','company','board','title','locations','source_url','first_seen_at','source_published_at','changed_fields','change_note','notice'];\nconst RUN_COLUMNS = ['run_id','status','started_at','finished_at','successful_boards','failed_or_skipped_boards','charged_board_checks','dataset_item_count','matched_signals','export_status','notes','run_url'];\nfunction text(value) { return value == null ? '' : String(value); }\nfunction safeCell(value) {\n const s=text(value).replace(/\\u0000/g,'');\n return /^[\\s]*[=+\\-@]/.test(s) ? \"'\"+s : s;\n}\nfunction validHttps(value) {\n // No Node imports are required in the n8n Code sandbox.\n const s=text(value);\n return /^https:\\/\\/[a-z0-9.-]+(?::[0-9]{1,5})?(?:[/?#][^\\s]*)?$/i.test(s) && !s.includes('\\\\') ? s : '';\n}\nfunction titleMatches(title, cfg) {\n const t=text(title).toLowerCase();\n const hasKeyword=k=>{\n const escaped=k.toLowerCase().replace(/[.*+?^${}()|[\\]\\\\]/g,'\\\\$&');\n return new RegExp('(?:^|[^a-z0-9])'+escaped+'(?:$|[^a-z0-9])').test(t);\n };\n return /\\b(engineer|engineering|developer|architect)\\b/.test(t)\n && cfg.titleKeywords.some(hasKeyword)\n && !cfg.excludeTitleKeywords.some(hasKeyword);\n}\nfunction locationsMatch(locations, cfg) {\n const t=(Array.isArray(locations)?locations:[]).join(' | ').toLowerCase();\n return !cfg.locationKeywords.length || cfg.locationKeywords.some(k=>t.includes(k.toLowerCase()));\n}\nfunction matches(event,cfg) {\n const job=event.job||{};\n if(titleMatches(job.title||event.title,cfg)&&locationsMatch(job.locations,cfg)) return true;\n // A role that changes out of the selected scope must still be visible to the consumer.\n if(event.event_type==='changed') {\n const previousTitle=event.changes?.title?.before ?? event.title;\n const previousLocations=event.changes?.locations?.before ?? job.locations;\n return titleMatches(previousTitle,cfg)&&locationsMatch(previousLocations,cfg);\n }\n return false;\n}\nfunction brief(value) {\n const s=typeof value==='string'?value:JSON.stringify(value);\n return text(s).slice(0,300)+(text(s).length>300?' … [excerpt]':'');\n}\nfunction filterEvents(events,cfg,runId) {\n if(!Array.isArray(events)) throw new Error('Dataset must be an array.');\n const seen=new Set(), result=[];\n for(const e of events) {\n if(!e || !LABELS[e.event_type] || (e.event_type==='baseline'&&!cfg.includeBaseline)) continue;\n if(!/^[a-f0-9]{64}$/.test(text(e.event_id))) throw new Error('A job event has no valid event_id.');\n if(seen.has(e.event_id)) continue;\n seen.add(e.event_id);\n if(!matches(e,cfg)) continue;\n const board=`${e.provider}:${e.board}`;\n const changed=Array.isArray(e.changed_fields)?e.changed_fields:[];\n const allChanges=changed.map(k=>`${k}: ${brief(e.changes?.[k]?.before)} → ${brief(e.changes?.[k]?.after)}`).join(' | ');\n const changeNote=allChanges.slice(0,5000)+(allChanges.length>5000?' … [excerpt]':'');\n const notice=e.event_type==='baseline' ? 'Present on the first scan; not a newly posted claim.'\n : e.event_type==='newly_observed' ? 'First seen by this monitor; the actual posting time may be earlier.'\n : e.event_type==='no_longer_listed' ? 'Absent across confirmed checks; hiring outcome is unknown.'\n : 'An observed source change; employer hiring intent is not verified.';\n result.push(Object.fromEntries(Object.entries({\n event_id:e.event_id,run_id:runId,observed_at:e.observed_at,signal:LABELS[e.event_type],\n company:cfg.companyNames?.[board]||e.board,board,title:e.title,\n locations:(e.job?.locations||[]).join(' | '),source_url:validHttps(e.source_url),\n first_seen_at:e.first_seen_at,source_published_at:e.job?.source_published_at,\n changed_fields:changed.join(', '),change_note:changeNote,notice\n }).map(([k,v])=>[k,safeCell(v)])));\n }\n return result;\n}\n\nconst cfg=$('Configure watchlist').first().json;\nconst run=$('Capture completed run').first().json;\nconst summaryResponse=$('Get run summary').first().json;\nconst summary=summaryResponse.statusCode===200?summaryResponse.body:null;\nconst expected=$('Check export size').first().json.itemCount;\nconst entries=$input.all().map(i=>i.json).filter(x=>Object.keys(x).length);\nif(entries.length!==expected) throw new Error('Incomplete dataset export for run '+run.id+': expected '+expected+', got '+entries.length+'. Replay this run; do not start another scan.');\nconst rows=filterEvents(entries,cfg,run.id);\nconst issues=!summary || run.status!=='SUCCEEDED' || Number(summary.failed_or_skipped_boards)>0;\nconst notes=[!summary?'SUMMARY unavailable; inspect the Actor log.':'',...(summary?.boards||[]).filter(b=>b.status!=='ok').map(b=>b.board+': '+(b.error||b.status))].filter(Boolean).join(' | ');\nreturn [{json:{rows,hasIssues:issues,run:{\n run_id:run.id,status:run.status,started_at:run.startedAt||'',finished_at:run.finishedAt||'',\n successful_boards:summary?.successful_boards??'',failed_or_skipped_boards:summary?.failed_or_skipped_boards??'',\n charged_board_checks:summary?.charged_board_checks??'',dataset_item_count:expected,matched_signals:rows.length,\n export_status:'PENDING',notes:safeCell(notes),run_url:'https://console.apify.com/actors/JKg4A48Voq8wYW3Wh/runs/'+run.id\n}}}];\n","mode": "runOnceForAllItems"},"id": "b32cac4e-5823-5bff-9914-e1a0b7298cd7","name": "Prepare recruiter output","type": "n8n-nodes-base.code","typeVersion": 2,"position": [3120,0]},{"parameters": {"jsCode": "return [{json:$('Prepare recruiter output').first().json.run}];","mode": "runOnceForAllItems"},"id": "83072307-926f-5388-bd29-dbdf4e92529a","name": "Prepare pending receipt","type": "n8n-nodes-base.code","typeVersion": 2,"position": [3360,0]},{"parameters": {"resource": "sheet","operation": "appendOrUpdate","documentId": {"__rl": true,"value": "={{ $('Configure watchlist').first().json.spreadsheetId }}","mode": "id"},"sheetName": {"__rl": true,"value": "Runs","mode": "name"},"columns": {"mappingMode": "defineBelow","value": {"run_id": "={{ $json.run_id }}","status": "={{ $json.status }}","started_at": "={{ $json.started_at }}","finished_at": "={{ $json.finished_at }}","successful_boards": "={{ $json.successful_boards }}","failed_or_skipped_boards": "={{ $json.failed_or_skipped_boards }}","charged_board_checks": "={{ $json.charged_board_checks }}","dataset_item_count": "={{ $json.dataset_item_count }}","matched_signals": "={{ $json.matched_signals }}","export_status": "={{ $json.export_status }}","notes": "={{ $json.notes }}","run_url": "={{ $json.run_url }}"},"matchingColumns": ["run_id"],"schema": [{"id": "run_id","displayName": "run_id","required": false,"defaultMatch": true,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "status","displayName": "status","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "started_at","displayName": "started_at","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "finished_at","displayName": "finished_at","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "successful_boards","displayName": "successful_boards","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "failed_or_skipped_boards","displayName": "failed_or_skipped_boards","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "charged_board_checks","displayName": "charged_board_checks","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "dataset_item_count","displayName": "dataset_item_count","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "matched_signals","displayName": "matched_signals","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "export_status","displayName": "export_status","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "notes","displayName": "notes","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "run_url","displayName": "run_url","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true}],"attemptToConvertTypes": false,"convertFieldsToString": false},"options": {"cellFormat": "RAW"}},"id": "0422519a-7969-53fa-a2ba-dbca1a30d45a","name": "Record pending export","type": "n8n-nodes-base.googleSheets","typeVersion": 4.6,"position": [3600,0]},{"parameters": {"conditions": {"options": {"caseSensitive": true,"leftValue": "","typeValidation": "strict","version": 2},"conditions": [{"id": "0e75dc5a-6be2-476f-8a45-af342424cdca","leftValue": "={{ $('Prepare recruiter output').first().json.rows.length > 0 }}","rightValue": "","operator": {"type": "boolean","operation": "true","singleValue": true}}],"combinator": "and"},"options": {}},"id": "d2bb50dd-b383-5128-818f-eed5b5b2196f","name": "Any matching signals?","type": "n8n-nodes-base.if","typeVersion": 2.2,"position": [3840,0]},{"parameters": {"jsCode": "return $('Prepare recruiter output').first().json.rows.map(json=>({json}));","mode": "runOnceForAllItems"},"id": "14d23b91-ec87-53c9-8d82-b7f4fb506e68","name": "Expand signal rows","type": "n8n-nodes-base.code","typeVersion": 2,"position": [4080,-100]},{"parameters": {"resource": "sheet","operation": "appendOrUpdate","documentId": {"__rl": true,"value": "={{ $('Configure watchlist').first().json.spreadsheetId }}","mode": "id"},"sheetName": {"__rl": true,"value": "Signals","mode": "name"},"columns": {"mappingMode": "defineBelow","value": {"event_id": "={{ $json.event_id }}","run_id": "={{ $json.run_id }}","observed_at": "={{ $json.observed_at }}","signal": "={{ $json.signal }}","company": "={{ $json.company }}","board": "={{ $json.board }}","title": "={{ $json.title }}","locations": "={{ $json.locations }}","source_url": "={{ $json.source_url }}","first_seen_at": "={{ $json.first_seen_at }}","source_published_at": "={{ $json.source_published_at }}","changed_fields": "={{ $json.changed_fields }}","change_note": "={{ $json.change_note }}","notice": "={{ $json.notice }}"},"matchingColumns": ["event_id"],"schema": [{"id": "event_id","displayName": "event_id","required": false,"defaultMatch": true,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "run_id","displayName": "run_id","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "observed_at","displayName": "observed_at","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "signal","displayName": "signal","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "company","displayName": "company","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "board","displayName": "board","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "title","displayName": "title","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "locations","displayName": "locations","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "source_url","displayName": "source_url","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "first_seen_at","displayName": "first_seen_at","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "source_published_at","displayName": "source_published_at","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "changed_fields","displayName": "changed_fields","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "change_note","displayName": "change_note","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "notice","displayName": "notice","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true}],"attemptToConvertTypes": false,"convertFieldsToString": false},"options": {"cellFormat": "RAW"}},"id": "a45ed9b5-50dd-53cf-b7fa-ab8a21c32b3f","name": "Upsert signals by event ID","type": "n8n-nodes-base.googleSheets","typeVersion": 4.6,"position": [4320,-100]},{"parameters": {"jsCode": "const p=$('Prepare recruiter output').first().json; return [{json:{...p.run,export_status:p.hasIssues?'COMPLETE_WITH_SOURCE_ISSUES':'COMPLETE'}}];","mode": "runOnceForAllItems"},"id": "de8d800e-ecd9-534e-8494-00782e4deda7","name": "Prepare completed receipt","type": "n8n-nodes-base.code","typeVersion": 2,"position": [4560,0]},{"parameters": {"resource": "sheet","operation": "appendOrUpdate","documentId": {"__rl": true,"value": "={{ $('Configure watchlist').first().json.spreadsheetId }}","mode": "id"},"sheetName": {"__rl": true,"value": "Runs","mode": "name"},"columns": {"mappingMode": "defineBelow","value": {"run_id": "={{ $json.run_id }}","status": "={{ $json.status }}","started_at": "={{ $json.started_at }}","finished_at": "={{ $json.finished_at }}","successful_boards": "={{ $json.successful_boards }}","failed_or_skipped_boards": "={{ $json.failed_or_skipped_boards }}","charged_board_checks": "={{ $json.charged_board_checks }}","dataset_item_count": "={{ $json.dataset_item_count }}","matched_signals": "={{ $json.matched_signals }}","export_status": "={{ $json.export_status }}","notes": "={{ $json.notes }}","run_url": "={{ $json.run_url }}"},"matchingColumns": ["run_id"],"schema": [{"id": "run_id","displayName": "run_id","required": false,"defaultMatch": true,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "status","displayName": "status","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "started_at","displayName": "started_at","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "finished_at","displayName": "finished_at","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "successful_boards","displayName": "successful_boards","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "failed_or_skipped_boards","displayName": "failed_or_skipped_boards","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "charged_board_checks","displayName": "charged_board_checks","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "dataset_item_count","displayName": "dataset_item_count","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "matched_signals","displayName": "matched_signals","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "export_status","displayName": "export_status","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "notes","displayName": "notes","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true},{"id": "run_url","displayName": "run_url","required": false,"defaultMatch": false,"display": true,"type": "string","canBeUsedToMatch": true}],"attemptToConvertTypes": false,"convertFieldsToString": false},"options": {"cellFormat": "RAW"}},"id": "80b600b6-147b-5003-8ada-6368fea394b0","name": "Record completed export","type": "n8n-nodes-base.googleSheets","typeVersion": 4.6,"position": [4800,0]},{"parameters": {"jsCode": "const p=$('Prepare recruiter output').first().json; if(p.hasIssues) throw new Error('Available signals were saved, but source checks need attention. See Runs tab and Apify run '+p.run.run_id+'.'); return [{json:{run_id:p.run.run_id,matched_signals:p.rows.length,export_status:'COMPLETE'}}];","mode": "runOnceForAllItems"},"id": "9726e4c7-701d-5386-8ac8-ff5727831549","name": "Report source issues","type": "n8n-nodes-base.code","typeVersion": 2,"position": [5040,0]}],"connections": {"Run manually": {"main": [[{"node": "Configure watchlist","type": "main","index": 0}]]},"Daily at 8am - inactive until enabled": {"main": [[{"node": "Configure watchlist","type": "main","index": 0}]]},"Configure watchlist": {"main": [[{"node": "Check spreadsheet headers","type": "main","index": 0}]]},"Check spreadsheet headers": {"main": [[{"node": "Validate sheet template","type": "main","index": 0}]]},"Validate sheet template": {"main": [[{"node": "Replay existing run?","type": "main","index": 0}]]},"Use saved run": {"main": [[{"node": "Get run status","type": "main","index": 0}]]},"Start JobDelta once": {"main": [[{"node": "Get run status","type": "main","index": 0}]]},"Get run status": {"main": [[{"node": "Check run state","type": "main","index": 0}]]},"Check run state": {"main": [[{"node": "Run has ended?","type": "main","index": 0}]]},"Wait before polling": {"main": [[{"node": "Get run status","type": "main","index": 0}]]},"Capture completed run": {"main": [[{"node": "Get run summary","type": "main","index": 0}]]},"Get run summary": {"main": [[{"node": "Get dataset metadata","type": "main","index": 0}]]},"Get dataset metadata": {"main": [[{"node": "Check export size","type": "main","index": 0}]]},"Check export size": {"main": [[{"node": "Get all dataset pages","type": "main","index": 0}]]},"Get all dataset pages": {"main": [[{"node": "Prepare recruiter output","type": "main","index": 0}]]},"Prepare recruiter output": {"main": [[{"node": "Prepare pending receipt","type": "main","index": 0}]]},"Prepare pending receipt": {"main": [[{"node": "Record pending export","type": "main","index": 0}]]},"Record pending export": {"main": [[{"node": "Any matching signals?","type": "main","index": 0}]]},"Expand signal rows": {"main": [[{"node": "Upsert signals by event ID","type": "main","index": 0}]]},"Upsert signals by event ID": {"main": [[{"node": "Prepare completed receipt","type": "main","index": 0}]]},"Prepare completed receipt": {"main": [[{"node": "Record completed export","type": "main","index": 0}]]},"Record completed export": {"main": [[{"node": "Report source issues","type": "main","index": 0}]]},"Replay existing run?": {"main": [[{"node": "Use saved run","type": "main","index": 0}],[{"node": "Start JobDelta once","type": "main","index": 0}]]},"Run has ended?": {"main": [[{"node": "Capture completed run","type": "main","index": 0}],[{"node": "Wait before polling","type": "main","index": 0}]]},"Any matching signals?": {"main": [[{"node": "Expand signal rows","type": "main","index": 0}],[{"node": "Prepare completed receipt","type": "main","index": 0}]]}},"active": false,"settings": {"executionOrder": "v1","timezone": "America/New_York","saveDataErrorExecution": "all","saveDataSuccessExecution": "all","executionTimeout": 1800},"pinData": {},"tags": []}