{
  "openapi": "3.0.1",
  "info": {
    "title": "Law Firm Jobs Monitor — Legal Job Alerts from BigLaw Portals",
    "description": "Law firm job openings straight from the portals firms hire through — viRecruit/viGlobal, cvMail, AllHires, WizeHire, iCIMS. 45 portals, robots.txt obeyed: 43 readable, 2 closed by theirs (2026-09-08). Or paste a portal URL. Monitor mode returns only NEW openings; status rows free.",
    "version": "0.1",
    "x-build-id": "fcBXFAh7cQQIAK8tQ"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/dockhand~legal-jobs-monitor/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-dockhand-legal-jobs-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/dockhand~legal-jobs-monitor/runs": {
      "post": {
        "operationId": "runs-sync-dockhand-legal-jobs-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/dockhand~legal-jobs-monitor/run-sync": {
      "post": {
        "operationId": "run-sync-dockhand-legal-jobs-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "firms": {
            "title": "Law firms to watch",
            "type": "array",
            "description": "Firm names from the built-in directory (e.g. `dechert`, `gibson dunn`, `mishcon de reya`, `farrer`, `skadden`) **or** a portal URL for any firm not in it. Portal URLs are auto-detected: viRecruit/viGlobal (`...viRecruitSelfApply/ReDefault.aspx`), cvMail (`fsr.cvmailuk.com/<slug>/...`), AllHires (`<firm>.allhires.com`), WizeHire (`wizehire.com/cmp/<slug>`) and iCIMS (`<tenant>.icims.com`). Firm not covered and you can't find its portal? Open an issue — portal requests are answered.",
            "items": {
              "type": "string"
            }
          },
          "monitorMode": {
            "title": "Monitor mode (only NEW openings since last run)",
            "type": "boolean",
            "description": "When on, the first run returns everything currently open and every later run returns only openings that appeared since — so a scheduled daily watch charges you for new roles only, never for the same listing twice. Openings you were not delivered (cut by the per-firm cap, held back by your charge limit, or pushed without a successful charge) are never remembered, so they come back. State is kept per watchlist in a key-value store named `legal-jobs-monitor-state` on your account; delete it to reset.",
            "default": false
          },
          "wizehireKeywords": {
            "title": "Also search WizeHire network-wide for these job titles",
            "type": "array",
            "description": "Small and boutique US law firms hire through WizeHire rather than a firm portal. These keywords search every WizeHire board at once — try `attorney`, `paralegal`, `legal assistant`, `estate planning`. Leave empty to skip.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "allFirms": {
            "title": "Scan every firm in the built-in directory",
            "type": "boolean",
            "description": "Ignores the list above and sweeps all portals shipped with this actor (US, Canada and UK). Slower — a full sweep measured 1,121 openings from the 43 readable portals in 223 seconds (2026-09-08, platform run n1DVFrXkkesLGbkQG, one request a second per host). Two directory portals (both viRecruit/viGlobal: Dorsey & Whitney and Lowenstein Sandler, whose robots.txt says `User-agent: * / Disallow: /` and nothing else) are closed by their own robots.txt as of 2026-09-08 and come back as free `robots-txt` rows; the other 43 are read. Best paired with monitor mode and title/location filters.",
            "default": false
          },
          "titleIncludes": {
            "title": "Only openings whose title contains…",
            "type": "array",
            "description": "Keep only titles containing ANY of these words (case-insensitive). Example: `associate`, `partner`, `paralegal`, `counsel`. Empty = keep all. Filtering happens BEFORE you are charged.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "titleExcludes": {
            "title": "Skip openings whose title contains…",
            "type": "array",
            "description": "Drop titles containing ANY of these words. Example: `summer`, `intern`, `trainee`, `secretary`.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "locationIncludes": {
            "title": "Only these locations",
            "type": "array",
            "description": "Keep only openings whose location contains ANY of these (case-insensitive). Example: `new york`, `london`, `chicago`. Empty = every office.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "maxJobsPerFirm": {
            "title": "Max openings per firm",
            "minimum": 0,
            "type": "integer",
            "description": "0 = no limit. Caps how many openings each portal DELIVERS per run, and therefore the cost. It never shortens the fetch: every source is still paged to the end of its feed, so `jobs_found` in RUN_SUMMARY is what the portal actually served, and `source_total` — where the source states one — is what it says it holds. In monitor mode, openings over the cap are NOT marked as seen: they arrive on your next run rather than disappearing.",
            "default": 0
          },
          "maxPagesPerSource": {
            "title": "Runaway page guard (advanced — leave at 0)",
            "minimum": 0,
            "type": "integer",
            "description": "0 = each platform's own measured default. Every source is paged to the END of its feed; this number is only a runaway guard so a broken feed cannot loop forever, and each default sits far above the widest board ever measured on that source (viRecruit 100 job categories, cvMail 200 pages of 30, WizeHire firm boards 100 pages of 100, the WizeHire keyword search 50 pages of the 500 we request, iCIMS 100 pages). Setting it LOWER deliberately shortens every fetch — the run still says so, in a free `results-truncated` row with `truncation_reason: \"our-guard\"` that names this input. The guards in force are written to `page_guards` in RUN_SUMMARY on every run.",
            "default": 0
          },
          "includeStatusRows": {
            "title": "Explain portals that returned nothing",
            "type": "boolean",
            "description": "Adds a free explanation row (`status` + `note`) instead of leaving a silent gap whenever a portal produces no job row, or whenever a fetch could not be completed. These rows are FREE and are never charged. The complete list of the TEN `status` values this actor can emit: `no-openings` (portal lists no open roles), `no-new-openings` (monitor mode, nothing new since your last run), `no-match` (roles found, none passed your title/location filters), `error` (portal did not answer — `note` has the reason), `unknown-firm` (name not in the directory and not a recognised portal URL), `charge-limit-reached` (your maxTotalChargeUsd stopped delivery — fires even when some rows were delivered; held-back rows are not marked seen), `charge-failed` (the platform could not record a charge, so the run stopped delivering; nothing marked seen), `results-truncated` (the fetch stopped before the feed ended — the row carries `truncation_reason`: `our-guard` = this actor's runaway guard stopped it and the note names `maxPagesPerSource`; `source-ceiling` = the source stated a total and then served fewer, both numbers in the note; `unknown` = the feed repeated a page instead of advancing), `partial` (a page or job category could not be read, so the rows delivered are real but incomplete — the note says how many parts were lost), `robots-txt` (the portal's host publishes a robots.txt that does not permit this actor to read that path, or the rules could not be read at all — so nothing was requested from it; the note names the path and the host, the rules are re-read every run, and no workaround is attempted). Where the page size is ours to choose (both WizeHire feeds), a `source-ceiling` note also lists the page sizes we asked at and states which of three things was measured: we asked for the whole feed in one request and were still refused the rest; or the source states a page-size maximum below its own total, so that request cannot be made and it refuses any page above the maximum IT stated; or the source stated no maximum and our own largest request fell below its total, which the note says plainly is where this actor stopped asking, not a limit the source set. Turn off if you want job rows only.",
            "default": true
          },
          "failOnSourceError": {
            "title": "Fail the run if a portal errors",
            "type": "boolean",
            "description": "Off by default: a firm whose portal is down is reported in the run summary (and as a free `status: error` row) and the rest still return. With it off the run itself fails only when EVERY portal you asked for failed and at least one of them refused to answer at all — a run in which every portal served a bot challenge instead is reported loudly (`sources_walled` in RUN_SUMMARY) and still succeeds, because a challenge is a fact about the network the run was made from, not about the portals. Turn on if you schedule this for alerting and want a failed run (and Apify's failure notification) the moment a portal breaks, challenge included.",
            "default": false
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}