Personio Vacancy Watch | Jobs Scraper & Change Monitor
Pricing
from $18.00 / 1,000 complete board checks
Personio Vacancy Watch | Jobs Scraper & Change Monitor
Follow new, changed and removed jobs on public Personio XML boards. Save job rows, keep state between runs, and export JSON, CSV or Excel.
Pricing
from $18.00 / 1,000 complete board checks
Rating
0.0
(0)
Developer
Cliqto Media
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
8 days ago
Last modified
Categories
Share
Watch known Personio boards for new, changed and removed jobs. This Personio jobs scraper reads public XML feeds and saves job rows or change events. It keeps the last complete board state when a check fails, so a failed check does not report every old job as removed.
Start with one board and snapshot mode. No Personio login is needed. Apify access is needed to run the Actor. A valid empty board or a delta with no changes can return zero rows. Platform usage can still have a cost; see Pricing and cost examples.

Table of contents
- What this Actor does
- What data you can get
- Quick start
- Full Input reference
- Input examples
- Output example
- Full Output field reference
- Pricing and cost examples
- Use cases
- Scheduling and monitoring
- API, MCP and integrations
- Limits and expected partial results
- Troubleshooting
- FAQ
- Related Actors
- Support
What this Actor does
Add known board names or exact HTTPS URLs on *.jobs.personio.de or *.jobs.personio.com. The Actor reads /xml?language=.... It does not search for companies, read applicant data, or open each job detail page.
| Mode or event | What you receive |
|---|---|
| snapshot | Current accepted jobs, each marked baseline. Every complete snapshot also updates saved state. |
| First delta for a board and language | Current jobs marked baseline. There is no earlier complete state to compare. |
| Later delta: new | A job ID that was absent from the last complete snapshot. |
| Later delta: changed | A saved job whose normalized core content changed. changedFields lists the field names. |
| Later delta: removed | A saved job ID absent from a new complete feed. A removal is an observation, not proof that a vacancy was filled. |
| Later delta: unchanged | Same content; emitted only with includeUnchanged=true. |
The monitor compares tenant|language inside the selected stateKey. Use different keys for independent monitors. The same tenant on .de and .com has the same state partition; use a separate stateKey if you need to compare these hosts independently.
What data you can get
Get job IDs, titles, office lists, department, employment type, seniority, schedule, creation date, descriptions, source salary values and public job URLs. Missing source values stay null or empty arrays. A language request can return no description if the employer has not supplied one in that language.
Existing consumers can read company, url, location, allLocations, postedAt, commitment and _scrapedAt. Their source meaning is listed below. Check these types and meanings before connecting an existing application. Full compatibility with every application is not promised.
team, country, workplaceType and updatedAt are null. The Actor does not infer a country or remote status from a title or office. Salary is returned only from published source salary content; it is not estimated from a role name.
Quick start
- Open the Actor you have access to in Apify Console, then open Input.
- Switch to JSON input and paste the minimal example below. Use a tenant from the company public Personio careers board, such as
teamative. A job title or search word such asmediabuyeris not a verified tenant. - Keep
languageas en and choose snapshot for this first check. - Click Start. Wait for the run to finish.
- Open Output or the run's default Dataset. Check
tenant,jobId,titleandeventType. - Open the default Key-value store and read
RUN_SUMMARY. Check each board'sstatusandcomplete.
If every board has a technical error, the run is Failed. Read RUN_SUMMARY in Storage; no new complete snapshot or removal events are saved. A real HTTP 404 is not_found and can finish without an exception. A redirect to the Personio home page is an error, not proof of an empty board.
In the captured example, teamative returned 18 jobs. Live jobs can change; 18 is an example count, not a required future result. For a repeated monitor, use delta and keep a stable stateKey. Start runs on the same key one at a time.
Full Input reference
The JSON object has 9 supported fields. Only boards is required. The fresh Input form offers teamative in snapshot mode, a 100-job cap and a separate PERSONIO_UI_DEMO_TEAMATIVE_EN state key. Repeated sample runs return current jobs without moving your normal monitor baseline. API calls still require boards; omitted optional fields use the runtime defaults below, including delta mode.
| Field | Input label | Type | Required | Runtime default | UI prefill | Allowed values | Effect and recommendation |
|---|---|---|---|---|---|---|---|
boards | Personio board names or URLs | array | Yes | No default; required. Start with one known board. | ["teamative"] | 1–50 non-empty strings | Reads each board in order. Use a slug such as teamative, or its exact HTTPS Personio board URL. Slugs use .de. |
language | Job text language | string | No | en | None | de, en, es, fr, it, nl, pl, pt | Requests this language from the XML feed. Start with en; try the source language if text is empty. It does not translate text. |
mode | Snapshot or changes | string | No | delta | snapshot | snapshot, delta | Snapshot returns current jobs as baseline rows. Delta compares saved state. Use snapshot for one-off exports and delta for repeated checks. |
maxJobsPerBoard | Jobs allowed per board | integer | No | 5000 | 100 | integer 1–5000 | If the board has more jobs, returns only this many with truncated status. It keeps old state and emits no removals. Choose a limit above your board size. |
maxBodyBytes | XML size limit (bytes) | integer | No | 10485760 | None | integer 16384–10485760 | Rejects an XML response above this size. Keeps old state. A 1 MiB limit (1048576) worked for the example boards; raise it for larger descriptions. |
boardTimeoutMs | Time limit per board (ms) | integer | No | 30000 | None | integer 1000–120000 | Limits the whole board fetch, including retries and waits. Keep 30000 first; a lower value can stop a slow board before all attempts. |
maxAttempts | Request attempts per board | integer | No | 3 | None | integer 1–5 | Includes the first request. Retries selected HTTP or transport failures within the board time limit. Use 1 to avoid retries or keep 3 for normal checks. |
stateKey | Monitor state key | string | No | PERSONIO_MONITOR_STATE | PERSONIO_UI_DEMO_TEAMATIVE_EN | 1–128 characters; pattern below | Selects a record in the named state store. Keep it stable for one monitor. Use a new key for an independent baseline. |
includeUnchanged | Show unchanged jobs | boolean | No | false | None | boolean true or false | Adds unchanged rows in delta mode. Keep false for a change feed. It has no effect in snapshot mode. |
stateKey accepts ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$. INPUT, OUTPUT, RUN_SUMMARY and STATE_SNAPSHOT are reserved. Null is not a default. An empty boards array, blank strings, duplicate canonical boards or unknown fields cause an input error.
Board URLs accept only an empty path, / or /xml. A single language query is allowed and must match the input language. Job detail URLs, ports, fragments, credentials, custom domains and redirects are not accepted. A slug must be one plain ASCII tenant label. Language codes are normalized to lowercase.
Settings that work together
| Settings | Result |
|---|---|
| language + URL query | A conflicting URL language is rejected. Language is part of saved board identity. |
| mode + includeUnchanged | includeUnchanged only adds rows in delta mode. Snapshot rows are baseline. |
| maxJobsPerBoard + state | A truncated feed can emit capped current events, but cannot replace saved state or emit removals. |
| maxAttempts + boardTimeoutMs | The board deadline includes retries and waits. More attempts cannot extend the deadline. |
| stateKey + board list | Boards not requested keep their old snapshots. Removing a board from input does not emit its jobs as removed. |
| snapshot + stateKey | A complete snapshot replaces that board's saved state. Use a separate key for one-off exports if you also run a delta monitor. |
Filters and reset
The Actor filters board hosts, XML shape and safe source URLs for valid input. It has no title, department, location, remote, date or salary search filters. Use the exported fields for your own filtering after the run. The job cap is a safety limit, not a search filter.
To start a new baseline, stop overlapping runs and choose a new stateKey, such as WEEKLY_PERSONIO_EN_V2. The old record stays intact. There is no reset flag and no state deletion in this workflow. To return to a monitor, restore its original key; it compares with that key's last complete snapshot.
Input examples
Minimal snapshot
{"boards": ["teamative"],"language": "en","mode": "snapshot"}
This requests one current English board. In the captured source scope, it yields baseline job rows. The example uses a safe state key; the captured input and full samples are retained in the repository.
Repeated three-board monitor
{"boards": ["teamative","personio","sipgate"],"language": "en","mode": "delta","maxJobsPerBoard": 100,"maxBodyBytes": 1048576,"boardTimeoutMs": 30000,"maxAttempts": 2,"stateKey": "WEEKLY_PERSONIO_EN","includeUnchanged": false}
Use this input unchanged for later checks. The first complete check on this key is a baseline. The next unchanged check can return zero rows. Three real boards returned 22 jobs in the checked snapshot and delta baseline, followed by zero repeat events. A future board may have more jobs than 100; check truncated status and raise the cap when needed.
Output example
This full job JSON is from a real teamative snapshot. The timestamp is the capture time, not a promise of current freshness. English description text was absent; the salary and offices came from the source.
{"_type": "job","_schemaVersion": "1.0","_source": "personio","tenant": "teamative","jobId": "2681315","title": "Junior SAP Funkčný Konsultant – SAP CE / SAP IS-U","publicUrl": "https://teamative.jobs.personio.de/job/2681315","applyUrl": null,"subcompany": "teamative Slovakia s.r.o.","office": "SK - Bratislava","additionalOffices": ["SK - Košice"],"department": "Delivery","recruitingCategory": "SK - application process IT/SAP","employmentType": "permanent","seniority": "entry-level","schedule": "full-time","yearsOfExperience": "1-2","keywords": ["SAP IS-U,SAP CE,ISU,CE,Junior"],"occupation": "enterprise_software_implementation_consulting","occupationCategory": "it_software","createdAt": "2026-06-22T11:07:27+00:00","descriptionSections": [],"descriptionText": null,"salaryInformation": {"minimum": 2000,"maximum": null,"currency": "EUR","period": "monthly"},"company": "teamative","url": "https://teamative.jobs.personio.de/job/2681315","location": "SK - Bratislava","allLocations": ["SK - Bratislava","SK - Košice"],"postedAt": "2026-06-22T11:07:27.000Z","commitment": "Full-time","team": null,"country": null,"workplaceType": null,"updatedAt": null,"observedAt": "2026-10-01T11:43:33.910Z","contentHash": "6b6d47abef37ae0392319d40644adbbefd20f280ac210875376111463339dbfe","_scrapedAt": "2026-10-01T11:43:33.910Z","eventType": "baseline","changedFields": []}
JSON output keeps arrays and objects. For export:
- Open the finished run's default Dataset.
- Choose Export and JSON, CSV or Excel (XLSX).
- Keep the identity, title and event fields when selecting columns.
- Check Unicode titles and office names in the downloaded file.
JSON, CSV and XLSX exports were checked on 18 real rows. Use JSON for full nested sections, salary and arrays. CSV/Excel flatten nested values; select columns for your consumer. A table view can hide columns without removing them from raw JSON. See Dataset export documentation.
Full Output field reference
There are 41 Dataset fields: 39 are always present, with nullable values where shown; previousObservedAt and removedAt are event-specific. Source fields and aliases describe the same job. Monitor fields describe this check.
Source job fields
| Field | Type | Empty or absent? | Example | Meaning and provenance |
|---|---|---|---|---|
_type | string | No | "job" | Fixed value job. Removed records are still job records. |
_schemaVersion | string | No | "1.0" | Record contract version 1.0; not the upstream XML version. |
_source | string | No | "personio" | Fixed source marker personio. |
tenant | string | No | "teamative" | Validated board slug. Use tenant + jobId as the job identity. |
jobId | string | No | "2681315" | Numeric XML position ID, stored as a string. Unique within a board. |
title | string | No | Junior SAP Funkčný Konsultant – SAP CE / SAP IS-U | XML job name, trimmed. XML entities are decoded once; CDATA text stays literal. |
publicUrl | string | No | "https://teamative.jobs.personio.de/job/2681315" | Built from the validated source host and jobId. |
applyUrl | string / null | Yes: null | null | Source value only when it is a safe same-host HTTPS /job/{jobId} URL, with no query or fragment. No invented apply link. |
subcompany | string / null | Yes: null | "teamative Slovakia s.r.o." | XML subcompany value. This may differ from the tenant slug. |
office | string / null | Yes: null | "SK - Bratislava" | First office value from XML. |
additionalOffices | array | Yes: [] | ["SK - Košice"] | Remaining office values and explicit additional offices. Empty array when absent. |
department | string / null | Yes: null | "Delivery" | XML department. No department filter is applied. |
recruitingCategory | string / null | Yes: null | "SK - application process IT/SAP" | XML recruiting category, kept separate from team. |
employmentType | string / null | Yes: null | "permanent" | XML employment type; no fixed job-type vocabulary is added. |
seniority | string / null | Yes: null | "entry-level" | XML seniority value; no seniority guess from the title. |
schedule | string / null | Yes: null | "full-time" | XML schedule value, such as full-time. |
yearsOfExperience | string / null | Yes: null | "1-2" | Source text, not a calculated number. |
keywords | array | Yes: [] | ["SAP IS-U,SAP CE,ISU,CE,Junior"] | Source keyword values; empty array when absent. |
occupation | string / null | Yes: null | "enterprise_software_implementation_consulting" | XML occupation value. |
occupationCategory | string / null | Yes: null | "it_software" | XML occupation category value. |
createdAt | string / null | Yes: null | "2026-06-22T11:07:27+00:00" | Original source creation date string. It is not a last-update time. |
descriptionSections | array | Yes: [] | [] | Source sections with title and content. Content can contain HTML; treat it as untrusted text. |
descriptionText | string / null | Yes: null | null | Text built from section headings and content with HTML tags removed. HTML character references are decoded once. Original HTML stays in descriptionSections. Null when no useful text is available. |
salaryInformation | object / null | Yes: null | {"minimum": 2000, "maximum": null, "currency": "EUR", "period": "monthly"} | Published salary values, including supported currencyCode/type names. Numbers may be read from source salary text. No salary prediction or currency conversion. |
Existing consumer aliases
| Field | Type | Empty or absent? | Example | Meaning and provenance |
|---|---|---|---|---|
company | string | No | "teamative" | tenant alias. A board identifier, not a verified legal company name. |
url | string | No | "https://teamative.jobs.personio.de/job/2681315" | publicUrl alias. |
location | string / null | Yes: null | "SK - Bratislava" | office alias. |
allLocations | array / null | Yes: null | ["SK - Bratislava", "SK - Košice"] | Unique office values, main office first. Null if no offices are supplied. |
postedAt | string / null | Yes: null | "2026-06-22T11:07:27.000Z" | An ISO date-time createdAt converted to UTC only when its calendar date, time and explicit timezone are valid. Null for a missing, invalid or timezone-free value. The original createdAt stays unchanged. |
commitment | string / null | Yes: null | "Full-time" | schedule with the first letter in uppercase. Null when absent. |
_scrapedAt | string | No | "2026-10-01T11:43:33.910Z" | Time of this check; on removed rows, this differs from the last-seen observedAt. |
team | null | Yes: null | null | Always null: there is no dedicated mapped source field. |
country | null | Yes: null | null | Always null: no country is inferred from office names. |
workplaceType | null | Yes: null | null | Always null: no remote or hybrid status is inferred. |
updatedAt | null | Yes: null | null | Always null: no reliable source update timestamp is mapped. |
Monitor extensions
| Field | Type | Empty or absent? | Example | Meaning and provenance |
|---|---|---|---|---|
eventType | string | No | baseline / new / changed / removed / unchanged | baseline, new, changed, removed or unchanged; see mode rules. |
observedAt | string | No | "2026-10-01T11:43:33.910Z" | Run-start UTC time for a current job. For removed rows, keeps the last time that job was seen. |
changedFields | array | Yes: [] | [] | Names of changed core fields, only for changed events. Empty array for other events. Alias names are not repeated. |
contentHash | string | No | 6b6d47abef37… (64 hex characters) | SHA-256 of 24 normalized core fields. Stable for unchanged content. Observation times and aliases are excluded. |
previousObservedAt | string | Absent on other events | ISO UTC; changed or removed only | Previous saved observation for changed or removed events. Absent for other event types. |
removedAt | string | Absent on other events | ISO UTC; removed only | UTC time when a complete feed no longer contains the job. Present only for removed events. It does not prove a role was filled. |
Nested job values
| Nested field | Type | Meaning / empty value / example |
|---|---|---|
descriptionSections[].title | string / null | Section name, or null. Example: Your profile. |
descriptionSections[].content | string | Source section body; may contain HTML. Example: Public job text . Never execute it. |
salaryInformation.minimum | number / null | Source minimum, e.g. 2000. |
salaryInformation.maximum | number / null | Source maximum; null in the selected live row. |
salaryInformation.currency | string / null | Source currency/currencyCode or recognizable source currency text, e.g. EUR. |
salaryInformation.period | string / null | Source period/type or recognizable source period text, e.g. monthly. |
XML & becomes & and & becomes &. CDATA is literal at the XML layer. Description text is then cleaned as HTML text, so its HTML entities follow text-cleaning rules. DOCTYPE/ENTITY declarations and malformed feeds are refused. These cases do not become valid empty boards.
The content hash includes 24 original core fields, with normalized whitespace and stable object key order. Arrays keep their order. Observation timestamps and consumer aliases do not trigger changed events. A section or office change can change the hash. changedFields lists original core names, such as office, not alias names such as location.
Where each result is saved
| Surface | What it contains |
|---|---|
| Default Dataset | All emitted job events from this run. Error details are in summaries, not extra job/error rows. |
| Default KVS INPUT | The run input. |
| Default KVS OUTPUT | Compact result, counts, board results and first up to 10 events. |
| Default KVS RUN_SUMMARY | Full board outcomes, attempts, resource counters and billing status. |
| Default KVS STATE_SNAPSHOT | Captured monitor state for this run, including saved snapshots from earlier checks. |
| Named KVS personio-jobs-monitor-state | Durable state under the chosen stateKey. Separate runs use this store for comparisons. |
| Output link jobs | Default Dataset API items. |
| Output link runOutput / runSummary / state | OUTPUT / RUN_SUMMARY / STATE_SNAPSHOT in the run's default KVS. |
An input error writes OUTPUT and RUN_SUMMARY and fails the process. A PPE persistence or billing failure writes BILLING_ERROR and fails the process; saved records stay available. If data writing stopped, OUTPUT, RUN_SUMMARY or STATE_SNAPSHOT may be absent. Check the Dataset, receipt and platform status too. Other runtime errors write error surfaces before the process fails. STATE_SNAPSHOT may be absent. An input error summary uses fallback mode/language/key values, not validated input. A partial board-check loop can finish with process success; inspect each board before using its rows. When every board has a technical error, or the run is cancelled, the process fails after saving diagnostics. An all-404 result can finish successfully with explicit not_found statuses, but it is not a complete empty XML snapshot. Always inspect the summary.
The sanitized KVS examples below use a replaced run ID and state key. The reduced state sample contains one job from the 18-job snapshot; it is not the full snapshot. Its job values and shape are unchanged.
OUTPUT
| Field | Type / presence | Meaning / example |
|---|---|---|
schemaVersion | string | Fixed personio-monitor-output/v1. |
runId | string | Apify run ID; EXAMPLE_RUN_ID in the sanitized sample. |
terminalOutcome | string | success, partial, error or cancelled. Read this before using rows. |
mode | string | snapshot or delta for this run. |
language | string | Requested language, e.g. en. |
observedAt | string | Run-start UTC timestamp. |
datasetItemCount | integer | Number of emitted Dataset rows, e.g. 18. |
eventCounts | object | Emitted counts by event type; see EventCounts below. |
boards | array | One BoardSummary for each requested board. |
dataset | object | Default Dataset selector and emitted item count. |
dataset.key | string | Fixed default; not an Apify Dataset ID. |
dataset.itemCount | integer | Same emitted row count as datasetItemCount. |
sample | array | First up to 10 compact event rows; empty when no rows are emitted. |
sample[].eventType | string | Event classification. |
sample[].tenant | string | Board slug. |
sample[].jobId | string | Source job ID. |
sample[].title | string | Source job title. |
sample[].contentHash | string | SHA-256 core hash. |
RUN_SUMMARY
| Field | Type / presence | Meaning / example |
|---|---|---|
schemaVersion | string | Fixed personio-monitor-run-summary/v1. |
runId | string | Apify run ID. |
mode | string | Run mode. |
language | string | Requested language. |
stateKey | string | Selected record in the named state store. |
startedAt | string | Run-start UTC timestamp. |
finishedAt | string | Monitor-finish UTC timestamp. |
durationMs | number | Monitor elapsed time, e.g. 734; not the full platform run duration. |
terminalOutcome | string | success, partial, error or cancelled. |
boardCount | integer | Number of board summaries. |
successfulBoardCount | integer | Boards with status success, including valid empty feeds. |
failedBoardCount | integer | Boards with other errors; truncated boards are counted separately. |
truncatedBoardCount | integer | Boards with too many positions for maxJobsPerBoard. |
datasetItemCount | integer | Number of emitted events. |
eventCounts | object | Counts of emitted events, not all unchanged jobs. |
boards | array | BoardSummary objects below. |
accounting | object | Resource counters and billing status below. |
redaction | object | Logging flags below; not a promise that source job text has no contacts. |
errorClass | string; optional | Top-level runtime/input/storage error code; absent on normal monitor completion. |
errorMessage | string; optional | Safe top-level diagnostic; absent on normal completion. |
EventCounts
| Field | Type / presence | Meaning / example |
|---|---|---|
baseline | integer | Number of emitted baseline rows; zero when none are emitted. |
new | integer | Number of emitted new rows; zero when none are emitted. |
changed | integer | Number of emitted changed rows; zero when none are emitted. |
removed | integer | Number of emitted removed rows; zero when none are emitted. |
unchanged | integer | Number of emitted unchanged rows; zero when none are emitted. |
BoardSummary (OUTPUT.boards[] and RUN_SUMMARY.boards[])
| Field | Type / presence | Meaning / example |
|---|---|---|
key | string | tenant|language, e.g. teamative|en. Host suffix is not a separate state partition. |
tenant | string | Canonical tenant slug. |
language | string | Requested language. |
sourceUrl | string | Canonical HTTPS XML request URL. |
status | string | success, truncated, not_found, access_block, transport_error, schema_drift, timeout, cancelled, security_error or body_cap. |
domainStatus | string / null | empty or non_empty after XML parsing; null on a failed fetch. |
complete | boolean | Whether the entire feed was accepted. Only true feeds replace saved board state. |
truncated | boolean | True when job count exceeds the cap. |
cap | integer | Input maxJobsPerBoard. |
jobCount | integer | Accepted positions, capped on a truncated feed; not the discarded total. |
emittedItemCount | integer | Rows emitted from this board; may include removals in delta. |
attempts | integer | Attempt-log length, including the first request. |
durationMs | number | Elapsed time for this board. |
errorClass | string / null | Failure code, or null on parsed success/truncation. |
errorMessage | string / null | Safe diagnostic, or null on parsed success/truncation. |
attemptLog | array | AttemptRecord objects below; [] if cancelled before fetching. |
AttemptRecord (boards[].attemptLog[])
| Field | Type / presence | Meaning / example |
|---|---|---|
attempt | integer | 1-based request attempt number. |
status | integer / null | HTTP status, e.g. 200; null if no status was captured. |
durationMs | number | Duration of this attempt. |
result | string | response, retry, failure or cancelled. |
errorClass | string; optional | Diagnostic code on an error/retry; absent on a normal response. |
retryAfterMs | number / null; optional | Server Retry-After parsed and bounded to 10000 ms; absent on other paths, null if unparseable. |
Accounting (RUN_SUMMARY.accounting)
| Field | Type / presence | Meaning / example |
|---|---|---|
currency | string | Fixed USD. |
cloudChargeUsd | number / null | Null in Cloud until platform read-back; local runs set 0 because no Cloud run is billed. |
billingStatus | string | pending_platform_readback in Cloud; not_applicable locally. Read final run usage in Console for a platform cost. |
externalSpendUsd | number | Fixed 0: the Actor does not call paid external services. This is not total platform billing. |
localResourceAccounting | object | Counters below; not dollar amounts. |
localResourceAccounting.networkRequests | integer | Number of recorded board attempts; retries included. |
localResourceAccounting.responseBytes | integer | Bytes counted from returned responses. A response interrupted inside the transport may not be counted here. |
localResourceAccounting.datasetItems | integer | Number of emitted Dataset rows. |
Redaction (RUN_SUMMARY.redaction)
| Field | Type / presence | Meaning / example |
|---|---|---|
rawResponsesLogged | boolean | Fixed false: raw XML is not written to runtime logs. |
credentialsLogged | boolean | Fixed false: source credentials are not accepted or logged. |
contactDetailsLogged | boolean | Fixed false for routine log messages. Public job descriptions in Dataset/state can still contain contacts. |
MonitorState (named stateKey and default STATE_SNAPSHOT)
| Field | Type / presence | Meaning / example |
|---|---|---|
schemaVersion | string | Fixed personio-monitor-state/v1. |
updatedAt | string | Last monitor-finish UTC timestamp; may advance while failed board snapshots stay unchanged. |
snapshots | object | Map by tenant|language; includes earlier boards not requested in this run. |
snapshots.<tenant|language> | object | StoredBoardSnapshot below. Missing if this board has never completed successfully. |
StoredBoardSnapshot
| Field | Type / presence | Meaning / example |
|---|---|---|
tenant | string | Board slug. |
language | string | Requested language. |
sourceUrl | string | Accepted XML feed URL. |
observedAt | string | Time this complete board was last seen. |
jobs | object | Map by jobId. Empty object for a complete empty feed. |
jobs.<jobId> | object | All JobCore source fields and aliases above, plus observedAt and contentHash. No eventType, changedFields, _scrapedAt, previousObservedAt or removedAt. |
OUTPUT sample
{"schemaVersion": "personio-monitor-output/v1","runId": "EXAMPLE_RUN_ID","terminalOutcome": "success","mode": "snapshot","language": "en","observedAt": "2026-10-01T11:43:33.910Z","datasetItemCount": 18,"eventCounts": {"baseline": 18,"new": 0,"changed": 0,"removed": 0,"unchanged": 0},"boards": [{"key": "teamative|en","tenant": "teamative","language": "en","sourceUrl": "https://teamative.jobs.personio.de/xml?language=en","status": "success","domainStatus": "non_empty","complete": true,"truncated": false,"cap": 100,"jobCount": 18,"emittedItemCount": 18,"attempts": 1,"durationMs": 586,"errorClass": null,"errorMessage": null,"attemptLog": [{"attempt": 1,"status": 200,"durationMs": 566,"result": "response"}]}],"dataset": {"key": "default","itemCount": 18},"sample": [{"eventType": "baseline","tenant": "teamative","jobId": "2819528","title": "Consultant Informationssicherheit / Notfallmanagement (BCM) (m/w/d)","contentHash": "98e456861e61a43c4900488870986d11809756fdf66c3ad1254f691a98aaa53e"},{"eventType": "baseline","tenant": "teamative","jobId": "2623782","title": "Initiativbewerbung","contentHash": "8e7d003519b5838ca1f61b6c1b2dde278fb7870415f5321248372431dcb3b069"},{"eventType": "baseline","tenant": "teamative","jobId": "2681315","title": "Junior SAP Funkčný Konsultant – SAP CE / SAP IS-U","contentHash": "6b6d47abef37ae0392319d40644adbbefd20f280ac210875376111463339dbfe"},{"eventType": "baseline","tenant": "teamative","jobId": "2805114","title": "Lead Consultant SAP Architecture","contentHash": "80861f66a6e54c531a6479c639d12f2242209d8e12905aefb59617caf2067aab"},{"eventType": "baseline","tenant": "teamative","jobId": "2617009","title": "Sales Manager – ServiceNow","contentHash": "68a21a83ee5658480905f028382ad56acf5cfcc31ce0e10f01bc46689ccaeec8"},{"eventType": "baseline","tenant": "teamative","jobId": "2231198","title": "SAP CS Consultant with German","contentHash": "c0ed5e0aa47157018b4b41e45a448afe6bc3836762fec57496821da2ef0520be"},{"eventType": "baseline","tenant": "teamative","jobId": "2232665","title": "SAP EWM Consultant","contentHash": "457f43f34971f3386bef18eb82188351dd0cf942ef78d781876142b96476addc"},{"eventType": "baseline","tenant": "teamative","jobId": "2670413","title": "SAP IS-U Consultant with German (m/f/d)","contentHash": "f3ae88c10b769487618d9a34566376bee59a840f162d7d29595439017022eef7"},{"eventType": "baseline","tenant": "teamative","jobId": "1896335","title": "SAP IS-U Konzultant pro Českou republiku (EU-remote)","contentHash": "6288fabc6ff238335bafca8ee8ddc6271450e501484b28ced77e1e12031dae28"},{"eventType": "baseline","tenant": "teamative","jobId": "2553655","title": "Senior Consultant ServiceNow ITOM / CMDB","contentHash": "8254686597814b510ffe3b6fb0b255306c2cf6df9cda39d37f6b0b6eec315989"}]}
RUN_SUMMARY sample
{"schemaVersion": "personio-monitor-run-summary/v1","runId": "EXAMPLE_RUN_ID","mode": "snapshot","language": "en","stateKey": "PERSONIO_EXAMPLE_SNAPSHOT","startedAt": "2026-10-01T11:43:33.910Z","finishedAt": "2026-10-01T11:43:34.644Z","durationMs": 734,"terminalOutcome": "success","boardCount": 1,"successfulBoardCount": 1,"failedBoardCount": 0,"truncatedBoardCount": 0,"datasetItemCount": 18,"eventCounts": {"baseline": 18,"new": 0,"changed": 0,"removed": 0,"unchanged": 0},"boards": [{"key": "teamative|en","tenant": "teamative","language": "en","sourceUrl": "https://teamative.jobs.personio.de/xml?language=en","status": "success","domainStatus": "non_empty","complete": true,"truncated": false,"cap": 100,"jobCount": 18,"emittedItemCount": 18,"attempts": 1,"durationMs": 586,"errorClass": null,"errorMessage": null,"attemptLog": [{"attempt": 1,"status": 200,"durationMs": 566,"result": "response"}]}],"accounting": {"currency": "USD","cloudChargeUsd": null,"billingStatus": "pending_platform_readback","externalSpendUsd": 0,"localResourceAccounting": {"networkRequests": 1,"responseBytes": 44712,"datasetItems": 18}},"redaction": {"rawResponsesLogged": false,"credentialsLogged": false,"contactDetailsLogged": false}}
Reduced STATE_SNAPSHOT sample
{"schemaVersion": "personio-monitor-state/v1","updatedAt": "2026-10-01T11:43:34.644Z","snapshots": {"teamative|en": {"tenant": "teamative","language": "en","sourceUrl": "https://teamative.jobs.personio.de/xml?language=en","observedAt": "2026-10-01T11:43:33.910Z","jobs": {"2681315": {"_type": "job","_schemaVersion": "1.0","_source": "personio","tenant": "teamative","jobId": "2681315","title": "Junior SAP Funkčný Konsultant – SAP CE / SAP IS-U","publicUrl": "https://teamative.jobs.personio.de/job/2681315","applyUrl": null,"subcompany": "teamative Slovakia s.r.o.","office": "SK - Bratislava","additionalOffices": ["SK - Košice"],"department": "Delivery","recruitingCategory": "SK - application process IT/SAP","employmentType": "permanent","seniority": "entry-level","schedule": "full-time","yearsOfExperience": "1-2","keywords": ["SAP IS-U,SAP CE,ISU,CE,Junior"],"occupation": "enterprise_software_implementation_consulting","occupationCategory": "it_software","createdAt": "2026-06-22T11:07:27+00:00","descriptionSections": [],"descriptionText": null,"salaryInformation": {"minimum": 2000,"maximum": null,"currency": "EUR","period": "monthly"},"company": "teamative","url": "https://teamative.jobs.personio.de/job/2681315","location": "SK - Bratislava","allLocations": ["SK - Bratislava","SK - Košice"],"postedAt": "2026-06-22T11:07:27.000Z","commitment": "Full-time","team": null,"country": null,"workplaceType": null,"updatedAt": null,"observedAt": "2026-10-01T11:43:33.910Z","contentHash": "6b6d47abef37ae0392319d40644adbbefd20f280ac210875376111463339dbfe"}}}}}
Optional PPE records
These records appear only when this run uses the supported pay-per-event model. They are in the default KVS, separate from your named monitor state. A receipt confirms platform event counts; it is not a customer invoice or proof of developer profit.
| Record / field | Type / example | Meaning |
|---|---|---|
| BILLING_RECEIPT.schemaVersion | string / personio-monitor-billing/v1 | Receipt format. |
| BILLING_RECEIPT.runId | string / run ID | Exact run; a different run cannot reuse it. |
| BILLING_RECEIPT.digest | string / 64 hex characters | SHA-256 of the result and planned counts. |
| BILLING_RECEIPT.phase | saving / persisted / confirmed | Data write started, data saved, or all event counts confirmed. |
| BILLING_RECEIPT.counts.complete-board-check | integer / 1 | Complete successful boards, including valid empty feeds. |
| BILLING_RECEIPT.counts.saved-current-job | integer / 18 | Unique current-job events from complete boards. Removed events are free. |
| BILLING_RECEIPT.calls.complete-board-check | ready / in_flight / confirmed | Board charge call not sent, uncertain, or confirmed by platform counts. |
| BILLING_RECEIPT.calls.saved-current-job | ready / in_flight / confirmed | Current-job charge call state. |
| BILLING_ERROR.runId | string / run ID | Run that failed during persistence or billing. |
| BILLING_ERROR.errorClass | string / storage_error | Safe failure class. |
| BILLING_ERROR.errorMessage | string / safe message | Reason to inspect results and platform counts. |
If a charge response is lost, the Actor reads platform counts. It does not send an uncertain charge again. If data saving was interrupted, it stops recovery to avoid duplicate rows. Preserve the run and contact support. Do not keep restarting it. A later independent run uses your saved monitor state and has its own billable checks.
Pricing and cost examples
Check the Pricing tab and your run limit before Start. This Actor uses pay per event. The Pricing tab shows whether platform usage is included or charged separately.
| Event | Price in USD | What you pay for |
|---|---|---|
| Complete board check | 0.018 | One complete successful board, including a valid empty feed. |
| Saved current job | 0.00045 | One unique baseline, new, changed or requested unchanged job from a complete board, saved to the Dataset. |
Removed jobs, failed/truncated boards and their job rows have no event fee. There is no start fee or extra default Dataset event fee. A check with no changes still has the board fee. For one complete board and18 current jobs, events costUSD0.0261. For three complete boards and22 current jobs, events costUSD0.0639. For three complete boards with no emitted current jobs, events costUSD0.054. These examples describe event charges; your actual counts may differ.
When the Pricing tab shows pay per event + usage, Apify charges platform usage separately, including for failed or partial runs. When usage is included, it is covered by the event price. A zero event fee does not promise zero platform resource use. Check both event fees and usage before scheduling.
The Actor saves results before charging. If the full result exceeds your run charge limit, the run fails without saving or charging that result. Source requests may already have used resources. If a billing call fails after saving, inspect BILLING_RECEIPT and BILLING_ERROR; do not repeatedly restart an uncertain charge.
The observations below are historical platform usage from private technical runs; they are not customer debit evidence for this PPE version.
| Observed scope | Emitted rows | Platform usage USD |
|---|---|---|
| One teamative snapshot, first checked run | 18 | 0.00045294 |
| Three boards, snapshot | 22 | 0.00044319 |
| Three boards, delta baseline | 22 | 0.00044465 |
| Same three boards, unchanged delta | 0 | 0.00031457 |
These runs used 256 MB and final platform usage read-back. They exclude build costs and future extra reads/exports. Billing meters can update after a run ends; RUN_SUMMARY.cloudChargeUsd stays null in Cloud. A zero-row delta still requests boards and reads/writes state. Fewer output rows do not mean a free check.
Use a platform run charge limit, a sensible timeout, and one test board before scheduling. maxJobsPerBoard limits accepted positions per board; it is not a dollar budget or a cap on removed events. No Cloud cost is claimed for 1000 or 5000 jobs from these small runs.
Use cases
| Buyer task | Input | Useful fields | Next step |
|---|---|---|---|
| Keep a current vacancy list | snapshot on known boards | tenant, jobId, title, publicUrl | Export JSON or CSV; replace your current list only from complete boards. |
| Track added or edited jobs | delta with a stable stateKey | eventType, changedFields, contentHash | Upsert by tenant + jobId in your database. |
| Mark a job as no longer listed | delta with complete source | removedAt, previousObservedAt, jobId | Archive the saved role; keep its last-known content. |
| Check locations and published pay | either mode | allLocations, salaryInformation | Filter these values in your own worksheet or application. |
Scheduling and monitoring
These are setup steps for your account, not a schedule created by this Actor.
- Run and check the intended delta input once.
- In Apify Console, open Schedules and Create new.
- Set the timezone and frequency, for example once per day.
- Select this Actor or a saved task with the same boards, language and stateKey.
- Select a tested build and set timeout, memory and charge limits.
- Leave enough time between runs for the full board list to finish. Do not overlap runs on the same key.
- After each run, read RUN_SUMMARY. Use its board complete/status fields before treating missing rows as domain events.
See Apify scheduling. Scheduled events can be delayed. This Actor has no state lock or automatic alert delivery; send notifications from your own workflow after checking eventType and terminalOutcome.
API, MCP and integrations
API
The API example uses this Actor's exact ID. Set PERSONIO_BUILD to the exact build number you have checked in Console. It does not use a moving build tag. Your account must have access to the Actor. Start creates a run and can incur a charge. Keep your token in APIFY_TOKEN in the environment. Do not put it in a URL, source file, screenshot or shared input.
# APIFY_TOKEN must already be set securely in your environment.# Set PERSONIO_BUILD to your checked build number (for example, 0.1.4 for the earlier runtime).: "${PERSONIO_BUILD:?Set the exact checked build number first}"curl --fail-with-body --request POST \"https://api.apify.com/v2/acts/vJ9HvIdGAVEGWQbxF/runs?build=${PERSONIO_BUILD}&memory=256&timeout=120&maxTotalChargeUsd=0.03" \--header "Authorization: Bearer ${APIFY_TOKEN:?Set APIFY_TOKEN securely first}" \--header 'Content-Type: application/json' \--data '{"boards":["teamative"],"language":"en","mode":"snapshot","stateKey":"API_PERSONIO_EN"}'
Read data.id from the response. Poll GET /v2/actor-runs/{RUN_ID} until it is terminal. Read data.defaultDatasetId and data.defaultKeyValueStoreId. Download GET /v2/datasets/{DATASET_ID}/items?format=json and GET /v2/key-value-stores/{KVS_ID}/records/RUN_SUMMARY, with the same Authorization header. For tabular exports use format=csv or format=xlsx. Check both platform status and the summary. See Run Actor API.
MCP and downstream tools
Apify provides an MCP server for supported Actors and storage tools. This Actor has no custom MCP server, and its execution through MCP has not been verified. Use the API above for the documented path. An existing authorized MCP storage tool may read a Dataset that your account can access; check that tool's permissions first.
Your own Node.js/Python app can read the Dataset API. You can also import CSV/XLSX into a spreadsheet. For an upsert, use tenant + jobId; use eventType for the action and contentHash for content checks. This Actor does not create a database, CRM record, webhook or notification service.
Limits and expected partial results
Input safety caps are 50 boards, 5000 positions per board and 10 MiB per response. Real Cloud checks covered up to 22 current jobs across 3 boards at 256 MB. A local synthetic test accepted 5000 positions on one board. The local test is not a Cloud capacity or throughput guarantee. Start small and inspect the results for your source.
| Situation | Dataset | Saved board state | How to read it |
|---|---|---|---|
| Complete feed with no jobs | No current jobs; delta may emit removals | Replaced with an empty job map | status=success, domainStatus=empty, complete=true. |
| Unchanged delta | Empty unless includeUnchanged=true | Updated complete snapshot | success and zero emitted events are normal. |
| More jobs than the cap | Capped current events; no removals | Old snapshot kept | status=truncated, complete=false, terminalOutcome=partial. |
| 404, block, bad XML, body cap or timeout | No events from that failed board | Old snapshot kept | Error status, complete=false; not an empty domain result. |
| Some boards fail | Successful/capped board events stay available | Only complete boards replace their state | terminalOutcome=partial; inspect boards individually. |
| All boards fail | No job events | Old board snapshots kept | terminalOutcome=error, even if platform process completed. |
Snapshots measure the source only at check time. Language availability, published fields, source changes and rate limits can affect output. Retry delays are bounded; only selected failures retry. Redirects and private/reserved network addresses are refused. No proxy or Personio credential input is supported.
The Actor reads public job feeds. It does not read applications, candidate profiles, private HR records or login-only data. Public descriptions may contain names, email addresses or phone numbers. They are not automatically redacted from Dataset/state. Review them before sharing. Follow source terms and your data-use duties. This is an independent product, not affiliated with or endorsed by Personio.
Troubleshooting
| Symptom | Likely reason | Check | Safe next action |
|---|---|---|---|
| Input error before requests | Bad board shape, duplicate board, reserved state key or unknown field | Input and top-level errorClass | Use the exact fields and valid board root URL. |
| Failed run with redirect_blocked | The tenant redirects away from its XML feed | RUN_SUMMARY and the company careers board | Use the actual public company tenant. Redirects stay blocked. Previous state is kept. |
| Empty delta Dataset | No changes, a valid empty board, or failed checks | terminalOutcome, status, domainStatus, complete | For no changes do nothing; for failure fix the input/source and retry. |
| descriptionText is null | Employer has no text in the requested language | descriptionSections and public source | Try the employer's language with a separate monitor key if desired. |
| Posted time is null but createdAt is filled | No explicit timezone or invalid source date | Original createdAt string | Keep the source value; do not invent a timezone. |
| Truncated status | Feed exceeds maxJobsPerBoard | cap and jobCount | Raise the cap within its range; previous complete state stays intact. |
| body_cap / timeout | Response is too large or slow | maxBodyBytes, boardTimeoutMs and attemptLog | Raise the relevant bound within the input range, then test one board. |
| not_found / access_block / schema_drift | Wrong board, denied access or changed XML | sourceUrl and safe errorMessage | Check the public board; report a reproducible source change. Do not treat it as all jobs removed. |
| Repeated new/changed events | Truncated snapshots, a changed key, or overlapping runs | complete, stateKey and run times | Keep one key, complete feeds and sequential runs. |
| State cannot be read | Unsupported or damaged saved state | Top-level error and named KVS record | Preserve the old record. Use a new key for a fresh baseline, then report the issue. |
FAQ
Do I need a Personio account?
No. Only known public XML boards are read. You need Apify access to run the Actor. Private job data is outside this workflow.
Can I use a custom careers domain or a job URL?
No. Use a plain board slug or the supported Personio board root/XML URL. A slug points to .de; use a full URL for .com.
Does removed mean the company filled the job?
No. It means the ID disappeared from a complete accepted feed. The employer may have hidden, moved or removed the listing for another reason.
Why is the first delta a baseline?
There is no prior complete board snapshot under this key and language. Later delta runs compare with the saved state. Changing the key starts a new comparison.
Does snapshot mode affect my monitor?
Yes. A complete snapshot updates the selected state record. Use a separate key for an export that should not move your delta baseline.
After an update that corrects derived fields, use snapshot mode to refresh all exported rows. Delta reports source content changes; it does not refresh every unchanged row. Use a separate key if you want to keep your existing monitor baseline.
Is createdAt the job's last change date?
No. It is the source creation value. updatedAt remains null. changed events compare content; they do not supply an employer update timestamp.
Are all fields always filled?
No. Null means the mapped value is absent or not safe to use. Empty arrays mean no values were supplied. Optional event timestamps can be absent. A job row with null source fields is different from a failed board fetch.
Are repeated runs free if no jobs change?
No free-run promise is made. Source checks and state work still use platform resources. Check the Pricing tab and run usage.
Related Actors
- Greenhouse Jobs Scraper & Monitor — use the owner's published Greenhouse tool when the source board is on Greenhouse.
- Lever Job Scraper — use the owner's published Lever tool for public Lever boards.
These are separate source tools. Check each tool's input and output before joining their data.
Support
Open the Actor's Issues tab for a question or reproducible bug. This is the support route listed by Cliqto Media. Include the run ID, board URL, language, mode and safe error text. Share only the fields needed to reproduce the issue. Never post tokens, cookies, private payloads or account billing details.