ERP sync guide

For HR administrators

This guide shows how to keep an ERP or HR system (Oracle HCM, SAP SuccessFactors, Odoo, an in-house system) and Jadiroon in step: your system stays the source of truth for the records it owns, and Jadiroon tells you about everything that changes on its side.

1. Plan who owns what

DataUsually owned byDirection
Organizational Structure (org units), positionsERPERP → Jadiroon
Employees (people on positions, managers)ERPERP → Jadiroon
Job descriptionsJadiroon (or ERP, if it holds them)either
Functional Structure catalog (sectors, sub-sectors, job families)JadiroonJadiroon → ERP (read only)
Candidates, openings, applications, interviews, offersJadiroonJadiroon → ERP
HiresJadiroonJadiroon → ERP (application.hired, employee.created)

Give the integration only the permissions for what it writes and reads (see Scopes).

Each workspace record can carry links to your ids: externalRefs: [{ system, externalId }]. Pick one system name per source system and environment pair you sync with (for example oracle-hcm) and keep it forever.

Write with upsert by external id:

curl -X PUT https://jadiroon.com/api/v1/employees/by-external/oracle-hcm/EMP-1002 \
  -H "Authorization: Bearer $JADIROON_KEY" -H "Content-Type: application/json" \
  -d '{
    "firstName": "Omar", "lastName": "Alqahtani",
    "manager": { "externalSystem": "oracle-hcm", "externalId": "EMP-1001" },
    "currentPosition": { "externalSystem": "oracle-hcm", "externalId": "POS-310" }
  }'
  • No record linked to oracle-hcm / EMP-1002: one is created from the body and linked; the answer is 201.
  • A linked record exists: the body is applied like a PATCH (omitted fields stay); the answer is 200.
  • Upserts exist for org units, positions and employees (PUT /api/v1/{org-units|positions|employees}/by-external/{system}/{externalId}), and in bulk for those plus candidates.
  • Job descriptions have no upsert endpoint: create them with POST /api/v1/job-descriptions and send externalRef: { externalSystem, externalId } in the body (or in a later PATCH) to link your id. Positions can then name them by your id.
  • Find a record by your id without changing it: GET /api/v1/employees?externalSystem=oracle-hcm&externalId=EMP-1002 (both parameters, or neither).
  • One external id links to one record, and one record has at most one id per system. Linking an id that is already taken returns 409 conflict, unless the record that held it no longer exists (for example it was deleted in the app): the old link is then dropped and the id links to the new record.

3. Reference other records by your ids

Bodies name related records either by Jadiroon id or by your id:

{ "orgUnit": { "id": "cmv…" } }
{ "orgUnit": { "externalSystem": "oracle-hcm", "externalId": "ORG-200" } }
FieldOnPoints to
parentorg unitorg unit
orgUnit, jobDescriptionpositionorg unit, job description
manager, currentPositionemployeeemployee, position
positionopeningposition
candidateapplicationcandidate

A reference that can't be found in your workspace returns 422 unresolved_reference, with the reference in details. Load parents before children: org units top-down, then job descriptions, then positions, then employees (managers before their reports, or a second pass to set manager).

4. Catalog rows: slugs, not ids

Sectors, sub-sectors and job families of the national catalog are shared by every workspace. Map them by their slug, which never changes and is the same in every Jadiroon environment. Ids are not: they differ per environment and change when a test workspace is reseeded. Never copy an id from one Jadiroon environment to another.

Rows your workspace created itself (custom families or sectors) have slug: null; link them with your own mapping table.

5. Keep Jadiroon's changes flowing back

Two options, often combined:

  • Webhooks (near real time): subscribe an endpoint to the events you care about, for example employee.* and application.hired. See Webhooks.
  • Polling: read each list with updatedSince and the deletion feed on a schedule. This is also the safety net if your endpoint was down longer than the retry window.

Every webhook payload and every object carries externalRefs, so you can match it to your record without storing Jadiroon ids.

  • A hard delete (in the app or the API) removes the record's links in the same step and writes a tombstone with the links it had, readable for 180 days at GET /api/v1/deleted.
  • If a linked record was deleted in the app and you upsert the same external id again, Jadiroon creates a new record and links it: you don't need to clean up first.
  • Hiring moves a candidate's links to the new employee record.
  • Offboarding (DELETE /api/v1/employees/{id}) keeps the record and its links; the status becomes FORMER_EMPLOYEE.

7. Checklist

  • One integration per environment, with the narrowest scope that works.
  • One system name per source system.
  • Load order: org units → job descriptions → positions → employees.
  • First load in bulk (see Bulk migration), then upsert changes one by one or in small batches.
  • Subscribe to webhooks and also poll daily with updatedSince + /deleted as a safety net.
  • Re-read the catalog lists (/sectors, /sub-sectors, /job-families) daily.