Skip to content
Journeez

Developer docs · v2.0 · October 2026

Integration specification

This page describes how Journeez exchanges data with a client system (CRM, ERP or WMS): how tasks are pulled, how fields are mapped, how imported tasks are handled, and which webhooks Journeez sends when the schedule changes.

1 endpoint

GET /tasks is the only mandatory endpoint.

5 fields

Required per task, in the client’s own field names.

4 events

Delivered to a single POST URL.

HMAC-SHA256

Optional signing, with a timestamp and an idempotency key on every delivery.

01

Overview

The integration has three parts. All of them are configured per integration in the Journeez dashboard.

01

Pull

On a configured schedule, Journeez calls the client’s task endpoint and fetches tasks changed since the last run. The client system can also trigger an immediate sync through an inbound notification.

02

Map

Each item is mapped to Journeez fields using JSONPath expressions, transforms, and translation tables for statuses and skills.

03

Write back

When a worker’s day is approved, changed or released, or a task is updated, Journeez sends a webhook to the client system.

Connectors

An integration is created from a catalog template: Generic REST, OData or GraphQL, or a native connector for Priority ERP, monday.com or SAP Business One. Alternatively, Set up with AI reads a sample response and proposes a mapping for review.

Handled by Journeez

  • Polling schedule and sync cursor.
  • Geocoding of task addresses.
  • Status translation, configured once in a table.
02

Pull API

Journeez initiates every request; the client system responds to GET /tasks. The path is configurable. Optional /employees and /locations endpoints are used to match workers and known sites.

SettingSupported values
AuthenticationBearer token, Basic, API key header, OAuth2 client credentials, or HMAC-signed requests (sha256=<hex> over <timestamp>.).
Paginationoffset / limit, page number, or cursor. Default page size 50, maximum 200.
Incremental filterRequired. The time of the last successful sync is sent in a configurable query parameter (default updatedAfter). Format is ISO-8601 UTC by default, or Unix seconds / milliseconds. Each call overlaps the previous one by 5 minutes.
Pull modeOnly what changed since the last pull (recommended): changed tasks only. All active tasks every time: a full snapshot on every call; required for closing tasks that disappear from the source.
FrequencyCron expression per integration. Templates default to every 15 minutes.
Import tasks created fromOptional. Tasks created before this date are not imported. Defaults to the day the integration is connected; clear it to import the full history. Requires the item’s creation-date field (JSONPath, e.g. $.createdAt). If the API can filter by creation date (e.g. ?createdAfter=), set that parameter name too and older tasks are not requested at all.
Timeout30 seconds per page.

Minimum valid item

Contains only the required fields. Field names are the client’s own and are mapped in configuration.

{
  "items": [
    {
      "id": 12345,
      "type": "AC unit replacement",
      "skill": "MECHANIC",
      "location": { "street": "Rothschild 12", "city": "Tel Aviv" },
      "updatedAt": "2026-06-01T08:14:22Z"
    }
  ]
}

Full item

Adds the recommended fields: status, planned times, duration, assignee, customer and coordinates.

{
  "items": [
    {
      "id": 12345,
      "type": "AC unit replacement",
      "status": "APPROVED",
      "scheduledStartTime": "2026-06-04T09:00:00Z",
      "scheduledEndTime": "2026-06-04T12:00:00Z",
      "duration": 180,
      "assignedToId": 22,
      "skill": "MECHANIC",
      "customer": "Acme Facilities",
      "location": {
        "street": "Rothschild 12",
        "city": "Tel Aviv",
        "lat": 32.0628,
        "lng": 34.7716
      },
      "updatedAt": "2026-06-01T08:14:22Z"
    }
  ],
  "nextCursor": "10481"
}
03

Field mapping

Each Journeez field is defined by a JSONPath into the source item, an optional transform and an optional default. The item array is located by a root path (default $.items[*]). A task missing any required field is not imported.

Journeez fieldTypeLevelNotes
originIdstringRequiredStable, unique task ID in the client system. Used for deduplication and returned in every webhook.
namestringRequiredTask label shown to dispatchers, typically the job type.
rolesstring[]RequiredRequired skills, translated through the role table. A single value can be wrapped into an array.
startAddressobjectRequiredstartAddress.city is the minimum. Add street, houseNumber and position.lat / position.lon for a precise location.
updatedAtISO-8601 UTCRequiredLast modification time in the client system. Used by the incremental filter.
date / startHourdate / timestampRecommendedTarget day and planned start. If no date is mapped, the current day is used.
durationnumber (hours)RecommendedStored in hours; apply minutesToHours to minute values. A missing or zero duration raises a data flag that blocks scheduling until corrected. Can also be derived by a rule.
endHourtimestampRecommendedPlanned end. If absent, computed as start plus duration.
providerOriginIdstringRecommendedClient-side ID of the assigned worker. Used when importing pre-scheduled tasks.
clientNamestringOptionalCustomer name. Matched to existing clients case-insensitively; see automatic client creation below.
priority, descriptionstringOptionalPriority is Normal, High or Critical after translation.

Transforms

Convert a source value into the type a Journeez field expects: dates, times, numbers, strings and arrays. One transform per rule, chosen from a fixed set of eleven.

Translation tables

Map client values to Journeez values: statuses (Status value mapping) and skills (Role / skill value mapping). Lookups are exact; status rows can apply inbound, outbound or both.

Derivation rules

Fill in duration, qualifications or location that the source system doesn’t have (for example, job type X → 2 hours). The first matching rule applies, top to bottom; if none match, a default is used or the row is flagged for review.

Mapping rules

Each Journeez field is configured with a rule made of the parts below. All parts are optional, but a rule needs at least one of from, template or default.

PartBehaviourExample
fromJSONPath into the source item. A wildcard path ([*]) returns an array.$.location.street
templateBuilds one string from several paths. Each {{path}} is resolved by JSONPath, null renders as empty, and the result is trimmed. Takes precedence over from.{{type}} - {{customer}}
defaultUsed when the extracted value is missing, null, an empty string or an empty array. Without from or template, it sets a constant."Normal"
transformConverts the value. One per rule; see the reference below.minutesToHours
translateWithLooks the value up in a translation table (statuses, roles). Arrays are translated element by element.roleTable

Processing order

  1. Extract the value with template, or else from.
  2. If the value is empty, apply default.
  3. Apply transform. For a wildcard path, it runs on each element.
  4. Apply translateWith, skipped if the transform failed.

If a transform cannot parse its input, the field is left empty and the error is recorded on the row with the field name and reason. It is visible in the sync run and in the test-mode preview. If the field is required, the task is not imported.

Example

"fields": {
  "originId":  { "from": "$.id", "transform": "toString" },
  "name":      { "template": "{{type}} - {{customer}}" },
  "roles":     { "from": "$.skill", "transform": "wrapArray",
                 "translateWith": "roleTable" },
  "date":      { "from": "$.scheduledStartTime",
                 "transform": "isoDateOrNow" },
  "startHour": { "from": "$.scheduledStartTime",
                 "transform": "extractHHmm" },
  "duration":  { "from": "$.duration",
                 "transform": "minutesToHours" },
  "startAddress.position.lat":
               { "from": "$.location.lat", "transform": "toFloat" },
  "priority":  { "default": "Normal" }
}

Translation tables

A translation table maps the values a client system sends to Journeez values. A field uses a table through translateWith. In the dashboard, the two tables are Status value mapping and Role / skill value mapping.

How a lookup works

Keys are matched exactly and are case-sensitive: "Approved" and "APPROVED" are different keys. When the value is an array, each element is looked up on its own.

Value receivedResult
Listed in the tableThe mapped value.
Not listed, table has _defaultThe _default value.
Not listed, no _defaultnull.
Not listed, table has _missing: "ERROR"A mapping error is recorded on the row for that field. _default is not used.
null or missing_default if set, otherwise null.

Status value mapping

  • Each row has a direction: → inbound (client → Journeez), ← outbound (Journeez → client) or ↔ both. Inbound rows form the forward table and outbound rows form a reverse table (statusReverse). If several rows map to the same Journeez status outbound, the first row is used.
  • Without a usable _default, the table decides which tasks are imported. A new task whose status is not listed is not created. An existing task that moves to an unlisted status is flagged for the dispatcher (see Import behaviour).
  • With a _default, every unlisted status resolves to that value and the task is imported.
  • Without a status table, the source value is passed through unchanged, so it must already be one of the six Journeez statuses.
  • Outbound rows are used when Journeez writes a status back through a native connector or an outbound mapping. A status with no outbound row is never guessed: the delivery fails visibly in the delivery history. The raw webhook payload always carries the untranslated Journeez status.

Role / skill value mapping

  • Inbound only. Maps the client’s skill codes to Journeez roles. When the source sends a single skill, apply wrapArray first.
  • A value that resolves to a role the organization doesn’t have does not block the import: the task is imported, keeps any roles that did resolve, and is flagged with the unmatched names. It cannot be scheduled until the role is added or the row is fixed.
  • A task left with no roles (for example, every value unlisted and no _default) is flagged as missing roles.
  • The dashboard lists values found in the sample that have no row, and can add them in one step.

Example

"translations": {
  "statusTable": {
    "APPROVED":    "Open",
    "DISPATCHED":  "Scheduled",
    "IN_PROGRESS": "InProgress",
    "COMPLETED":   "Closed",
    "CANCELLED":   "Closed"
  },
  "statusReverse": {
    "Scheduled": "DISPATCHED",
    "Closed":    "COMPLETED"
  },
  "roleTable": {
    "MECHANIC": "Mechanic",
    "ELEC":     "Electrician",
    "_default": "General technician"
  }
}

Transform reference

These are the only transforms available. An unknown name is a configuration error. Unless noted, null, missing and empty-string inputs return null without an error.

TransformBehaviourInput → outputTypical use
isoDateParses a date or datetime and returns ISO-8601 UTC. Fails on unparseable input."2026-06-04 09:00" → "2026-06-04T09:00:00.000Z"Non-standard date strings.
unixToIsoConverts a Unix timestamp (number or numeric string) to ISO-8601 UTC. Values of 10¹¹ and above are treated as milliseconds, smaller values as seconds."1775773020000" → "2026-04-09T22:17:00.000Z"Epoch timestamps, which isoDate does not accept as strings.
isoDateOrNowReturns the calendar day of the input in the organization’s time zone, at midnight UTC. Empty or whitespace input becomes today.null → today, e.g. "2026-10-21T00:00:00.000Z"date when unscheduled work should appear on today’s board.
extractHHmmReturns the time of day as HH:mm, taken as written in the source. Offsets are not converted to UTC."2026-05-31T08:15:00+03:00" → "08:15"startHour / endHour from a datetime field.
toFloatConverts to a number, keeping decimals. Fails on non-numeric input."32.0628" → 32.0628Coordinates sent as strings.
toIntConverts to an integer, dropping any fractional part. Fails on non-numeric input."90.7" → 90Counts and numeric IDs sent as strings.
minutesToHoursDivides by 60.90 → 1.5duration supplied in minutes.
toStringConverts a value to a string.12345 → "12345"Numeric IDs, e.g. originId.
concatJoins an array with single spaces, skipping empty elements. A non-array is returned as a string; null becomes "".["Herzl", null, "5"] → "Herzl 5"Building one value from a wildcard path.
splitArrayItemReturns the first element of an array. A non-array is returned unchanged.["MECHANIC"] → "MECHANIC"A wildcard path that yields a single value.
wrapArrayWraps a single value in an array. Arrays pass through unchanged; empty input becomes []."MECHANIC" → ["MECHANIC"]roles when the source has one skill. Combine with translateWith.

Values that need a shape not listed here, such as unit conversions other than minutes to hours, should be prepared in the client system before the response is returned.

04

Import behaviour

Test mode

Enabled by default on new integrations. Data is fetched and mapped, but nothing is written. Use the connection test and the live preview, then turn test mode off to go live.

Status filtering

Without a _default in the status table, a new task is created only if its status is listed. If an existing task moves to an unlisted status (for example, on hold), it is flagged for the dispatcher. Optionally, Delete tasks with an unmapped status removes such tasks while they are still open and unscheduled.

Closing tasks

Terminal status (recommended): return the task with a completed or cancelled status mapped to Closed. Close tasks that disappear from the source: available only with “All active tasks every time”; tasks no longer returned are closed. In both cases the assignment history is retained.

Pre-scheduled tasks

When a client status is mapped to Scheduled and the item includes a worker, a date, a start time and an end or duration, the task is placed on that worker’s day and excluded from automatic optimization. Conflicting placements are applied and flagged for review. When one of those values is missing, Keep scheduled tasks that can’t be placed keeps the task and flags it; otherwise it is skipped.

Manual edits

A field corrected by a dispatcher in Journeez is not overwritten by later syncs unless the source value changes.

Auto-create customers

Off by default. When on, a clientName that doesn’t match an existing customer creates a new customer record on import.

05

Task statuses

Journeez uses six statuses. Client statuses are translated through the status table. Closed is the only terminal status and covers both completion and cancellation; the client system remains the source of truth for that distinction.

  1. 01OpenNot placed on a worker’s day.
  2. 02ScheduledPlaced on a worker’s day.
  3. 03OnRouteWorker is travelling to the site.
  4. 04OnSiteWorker has arrived.
  5. 05InProgressWork has started.
  6. 06ClosedCompleted or cancelled.

OnRoute, OnSite and InProgress are set by the Journeez mobile app.

Actions and events

ActionEventJourneez statusTypical client status
Task created in the client system— (pulled)OpenWaiting to be scheduled
Task placed on an approved dayschedule.upsertedScheduledScheduled; worker and time stored
Time moved or worker replacedschedule.upserted (day re-sent)ScheduledUnchanged; worker and time updated
Task removed from an approved dayschedule.deletedOpenWorker and time cleared; back to waiting
Task edited, status changed, or closed in the fieldtask.updatede.g. Closede.g. pending manager approval
Task deleted in Journeeztask.deleted—Client-defined
06

Outbound webhooks

All events are sent as POST requests to one URL. Schedule events carry assignments (worker, day, times). Task events carry task data only and never include assignment information.

EventDashboard labelSent when
schedule.upsertedSchedule confirmedA worker’s day is approved, or an approved day (or the current day) is saved.
schedule.deletedSchedule clearedAn approved day is un-approved, cleared or deleted. The listed bookings should be released.
task.updatedTask changedA task is created, edited, changes status or is closed.
task.deletedTask removedA task is deleted in Journeez.

Schedule delivery

Only when confirmed (default): schedule events are sent for approved days and for the current day. Unapproved future days are not sent. Every update: unapproved days are sent as well. The mode applies to schedule events only; task events are always sent.

Receiver requirements

  • Return 2xx within 15 seconds.
  • Deduplicate on X-Idempotency-Key.
  • Apply each body as current state, not as a delta.
  • On task events, treat null as “no data” and leave the field unchanged.
  • Return 200 and write nothing when x-journeez-webhook-test: true is present.

Headers

HeaderValueSent when
X-Journeez-Signaturesha256=<hex>An HMAC secret is configured
X-Journeez-TimestampUnix secondsAn HMAC secret is configured
X-Idempotency-Keyoutbox-<n>Always
AuthorizationBearer <token>A bearer token is configured
X-API-Key (name configurable)API keyAn API key is configured
x-journeez-webhook-testtrueConnectivity tests only

At least one of bearer token, API key or HMAC secret must be configured before webhooks can be enabled.

Payloads

schedule.upserted

One worker, one day. confirmedDate is present only when the event was produced by approving the day.

{
  "version": 1,
  "event": "schedule.upserted",
  "orgId": 63,
  "confirmedDate": "2026-07-23",
  "schedule": {
    "date": "2026-07-23",
    "endDate": "2026-07-23",
    "providerId": 773,
    "providerOriginId": "97",
    "resourceId": null,
    "isPlanned": true,
    "tasks": [
      {
        "id": 31798,
        "originId": "82600020",
        "startHour": "2026-07-23T08:00:00.000",
        "endHour": "2026-07-23T09:00:00.000",
        "providerOriginId": "97"
      }
    ]
  }
}

schedule.deleted

Same shape as schedule.upserted. null times and worker indicate a released booking; originId identifies it.

{
  "version": 1,
  "event": "schedule.deleted",
  "orgId": 63,
  "schedule": {
    "date": "2026-07-23",
    "providerId": 773,
    "providerOriginId": "97",
    "resourceId": null,
    "isPlanned": true,
    "tasks": [
      {
        "id": 31798,
        "originId": "82600020",
        "startHour": null,
        "endHour": null,
        "providerOriginId": null
      }
    ]
  }
}

task.updated

duration is in minutes. status is the untranslated Journeez status. Fields can be restricted per integration; id and originId are always included. task.deleted uses the same envelope.

{
  "version": 2,
  "event": "task.updated",
  "orgId": 63,
  "task": {
    "id": 4711,
    "originId": "12345",
    "status": "Closed",
    "duration": 90,
    "name": "Brake inspection",
    "description": "Rear axle noise reported",
    "priority": "Normal",
    "clientId": 812,
    "startAddress": {
      "displayName": "Herzl 5, Tel Aviv",
      "street": "Herzl",
      "houseNumber": "5",
      "city": "Tel Aviv",
      "position": { "lat": 32.0668, "lon": 34.7647 }
    },
    "endAddress": null
  }
}

Verifying the signature

HMAC-SHA256 of <timestamp>.<raw body>, computed over the raw body before JSON parsing.

import { createHmac, timingSafeEqual } from "crypto";

function verify(rawBody, headers, secret) {
  const ts = headers["x-journeez-timestamp"];
  const received = headers["x-journeez-signature"];

  // Replay protection: reject more than 5 minutes of clock skew.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected = "sha256=" + createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(received ?? "");
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}
07

Errors and retries

Pull requests

ResponseJourneez behaviour
200Page applied; cursor advanced.
400 / 422 / other 4xxRun fails. An {"error": "..."} body is shown in the dashboard.
401 / 403Run fails; not retried. After 3 consecutive permanent failures, pulling is paused until the configuration is fixed.
429Retried on the next scheduled run.
5xx, timeout, networkRetried in the same run after 1, 2 and 4 seconds, then on the next scheduled run.

Webhook deliveries

ResponseJourneez behaviour
2xxMarked as delivered.
400 401 403 409 410 422Permanent failure. Moved to the dead-letter queue without retry; can be replayed from the dashboard.
429 / 503Retried, honouring Retry-After up to 1 hour.
404, other 5xx, timeoutRetried after 1s, 5s, 30s, 2m, 10m and 1h (7 attempts in total), then dead-lettered.

When several pending events exist for the same day or task, only the most recent is delivered.

08

Edge cases

Addresses without a street

Common for construction sites and new developments. When only a city is provided, the task is geocoded to the city centre, the address is marked approximate, and a dispatcher is asked to confirm it.

Team tasks

A task can require several workers with different skills at the same site. All team members share the same start and end time. Pre-scheduled team tasks are not placed on import; they are flagged for the dispatcher.

Vehicle and asset schedules

A schedule for a vehicle or asset has providerOriginId: null, the same as a released booking. Use resourceId to distinguish the two.

09

Go-live checklist

  1. GET /tasks returns the five required fields and honours the incremental filter parameter.
  2. The connection test succeeds and the mapped-row preview is correct in test mode.
  3. The status and role tables cover every value the client system returns.
  4. The webhook receiver returns 200 without writing data when x-journeez-webhook-test: true is present (the Send test event button).
  5. The receiver deduplicates on X-Idempotency-Key and, if an HMAC secret is configured, verifies the signature.
  6. The receiver handles all four events, including schedule.deleted with null values.
  7. Test mode is disabled.