# Business Made - Journeys

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /business-made/journeys/accounts/{employeeId}

**A person's accounts in other apps**

`operationId: JourneyAccountsController_list`

Every account the person has in other apps — created automatically by provisioning or recorded by hand on a journey task — with provider, app, email, external id, status and groups. HR, an admin, or the person's manager.

#### Signature

```http
GET /business-made/journeys/accounts/{employeeId} (employeeId: string) -> `{ data:[{key, provider, app, label, email, externalId, status, groups, updatedAt, note, by, automated}], employeeId, name }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | employee-not-found | Employee not found | No employee has that id. | — |
| `403` | hr-only | Only HR, an admin or the person’s manager can see their accounts | The caller is none of those. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code or bm_employee sk. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ data:[{key, provider, app, label, email, externalId, status, groups, updatedAt, note, by, automated}], employeeId, name }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR, an admin or the person’s manager can see their accounts — The caller is none of those. |
| `404` | Employee not found — No employee has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/journeys/cohorts

**List cohorts**

`operationId: JourneyLifecycleController_listCohorts`

Groups of people started together (a class of new hires), newest first, each with its progress: journeys, open, complete, cancelled, at risk, task counts and % done. HR or admin only.

#### Signature

```http
GET /business-made/journeys/cohorts () -> `{ data:[{cohortId, name, event, startDate, lastStartDate, journeys, cancelled, open, complete, atRisk, counts, pct, createdAt}] }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ data:[{cohortId, name, event, startDate, lastStartDate, journeys, cancelled, open, complete, atRisk, counts, pct, createdAt}] }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/cohorts

**Start a cohort**

`operationId: JourneyLifecycleController_startCohort`

Starts the same journey (`event`, default onboarding) for up to 500 people at once, optionally with chosen templates, an effective date, one buddy and a welcome page for all. Up to 10 people start inline; a larger cohort is queued as a one-off job and the response says when it runs (`queued: true`, `runAt`). HR or admin only.

#### Signature

```http
POST /business-made/journeys/cohorts (body) -> `{ cohortId, name, queued: false, created, failed }` — or `{ cohortId, name, queued: true, people, runAt }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |
| `400` | name-required | Give the cohort a name | `name` is empty. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

```json
{
  "name": "October line cooks",
  "employeeIds": [
    "EMP-1043",
    "EMP-1044"
  ],
  "buddyId": "EMP-1001"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | `{ cohortId, name, queued: false, created, failed }` — or `{ cohortId, name, queued: true, people, runAt }` |
| `400` | Give the cohort a name — `name` is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/journeys/cohorts/{cohortId}

**One cohort with its journeys**

`operationId: JourneyLifecycleController_cohort`

The cohort summary, the state of its queued start when it was queued (`queue: { status: queued | done, runAt, result, people }`), and every journey in it (planned, in progress, complete or cancelled). HR or admin only.

#### Signature

```http
GET /business-made/journeys/cohorts/{cohortId} (undefined: string) -> `{ cohort, queue, journeys }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |
| `404` | cohort-not-found | Cohort not found | No journeys and no queued start carry that cohort id. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `cohortId` | path | string | yes |  |
| `undefined` | path | string | yes |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ cohort, queue, journeys }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `404` | Cohort not found — No journeys and no queued start carry that cohort id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/cohorts/{cohortId}/cancel

**Cancel a cohort**

`operationId: JourneyLifecycleController_cancelCohort`

Cancels every open journey in the cohort with the reason. Journeys that fail to cancel are reported, not fatal. HR or admin only.

#### Signature

```http
POST /business-made/journeys/cohorts/{cohortId}/cancel (undefined: string, body) -> `{ cohortId, cancelled, failed:[{journeyId, message}] }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |
| `400` | reason-required | reason is required | `reason` is empty. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `cohortId` | path | string | yes |  |
| `undefined` | path | string | yes |  |

### Request body

```json
{
  "reason": "Opening postponed"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | `{ cohortId, cancelled, failed:[{journeyId, message}] }` |
| `400` | reason is required — `reason` is empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/cohorts/{cohortId}/reschedule

**Move a cohort's start date**

`operationId: JourneyLifecycleController_rescheduleCohort`

Moves everyone's start date: each hire's `employment.startDate` changes and their journey re-plans from it. People who fail are reported. HR or admin only.

#### Signature

```http
POST /business-made/journeys/cohorts/{cohortId}/reschedule (undefined: string, body) -> `{ cohortId, moved, failed }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |
| `400` | start-date-invalid | startDate must be YYYY-MM-DD | `startDate` is not YYYY-MM-DD. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `cohortId` | path | string | yes |  |
| `undefined` | path | string | yes |  |

### Request body

```json
{
  "startDate": "2026-10-19"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | `{ cohortId, moved, failed }` |
| `400` | startDate must be YYYY-MM-DD — `startDate` is not YYYY-MM-DD. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/rehire

**Rehire a former employee**

`operationId: JourneyLifecycleController_rehire`

For someone who has left (terminated, or inactive with an end date): records their previous stint in the employment history, resets the record (status inactive until day one, new start and hire date, end date cleared, any changed title / department / location / supervisor / employment type applied) and starts a `rehire` journey. Returns the journey detail. HR or admin only.

#### Signature

```http
POST /business-made/journeys/rehire (body) -> The journey detail (as GET /business-made/journeys/{id})
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |
| `400` | employee-required | employeeId is required | `employeeId` is missing. | — |
| `404` | employee-not-found | Employee EMP-0877 not found | No employee has that id. | — |
| `409` | not-a-leaver | Ana Ruiz has not left — only a former employee can be rehired | The person is still employed. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

```json
{
  "employeeId": "EMP-0877",
  "startDate": "2026-11-02",
  "location": "harbor-grill"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The journey detail (as GET /business-made/journeys/{id}) |
| `400` | employeeId is required — `employeeId` is missing. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `404` | Employee EMP-0877 not found — No employee has that id. |
| `409` | Ana Ruiz has not left — only a former employee can be rehired — The person is still employed. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/transfer

**Transfer or change a role**

`operationId: JourneyLifecycleController_transfer`

Starts a `transfer` journey (when the location changes) or a `role_change` journey (department, title or supervisor only), applied at `effectiveDate` by the journey. Only fields that actually differ count. Returns the journey detail. HR or admin only.

#### Signature

```http
POST /business-made/journeys/transfer (body) -> The journey detail
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |
| `400` | employee-required | employeeId is required | `employeeId` is missing. | — |
| `409` | terminated | A former employee cannot be transferred — rehire them | The person has left. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

```json
{
  "employeeId": "EMP-1043",
  "effectiveDate": "2026-11-01",
  "location": "dockside"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The journey detail |
| `400` | employeeId is required — `employeeId` is missing. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `409` | A former employee cannot be transferred — rehire them — The person has left. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/journeys/employee/{employeeId}/history

**A person's employment history**

`operationId: JourneyLifecycleController_history`

Their earlier stints and changes (`employmentHistory`), every journey they have had, and the equipment issued to them. HR or admin only.

#### Signature

```http
GET /business-made/journeys/employee/{employeeId}/history (employeeId: string) -> `{ employeeId, employmentHistory, journeys, equipment }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |
| `404` | employee-not-found | Employee EMP-1043 not found | No employee has that id. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `employeeId` | path | string | yes | Employee code or bm_employee sk. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ employeeId, employmentHistory, journeys, equipment }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `404` | Employee EMP-1043 not found — No employee has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /business-made/journeys/{id}/welcome

**Set a journey's welcome page**

`operationId: JourneyLifecycleController_welcome`

What the hire sees before day one: `message`, `arriveTime`, `arriveAt`, `dressCode`, `whatToBring`, `parking`, `contactName`, `contactPhone` (sent flat or under `welcome`; other fields are ignored). Returns the journey detail. HR or admin only.

#### Signature

```http
PUT /business-made/journeys/{id}/welcome (id: string, body) -> The journey detail
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Journey id (bm_journey sk). |

### Request body

```json
{
  "message": "Welcome to the team!",
  "arriveTime": "08:30",
  "arriveAt": "Back door by the loading dock",
  "dressCode": "Black non-slip shoes"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The journey detail |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /business-made/journeys/{id}/buddy

**Set a journey's buddy**

`operationId: JourneyLifecycleController_buddy`

Names the buddy. Open tasks assigned to the buddy move to the new one; tasks already done stay with whoever did them. Returns the journey detail. HR or admin only.

#### Signature

```http
PUT /business-made/journeys/{id}/buddy (id: string, body) -> The journey detail
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |
| `400` | buddy-required | buddyId is required | `buddyId` is missing. | — |
| `409` | journey-closed | This journey is closed | The journey is complete or cancelled. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Journey id (bm_journey sk). |

### Request body

```json
{
  "buddyId": "EMP-1001"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The journey detail |
| `400` | buddyId is required — `buddyId` is missing. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `409` | This journey is closed — The journey is complete or cancelled. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/journeys/templates

**List journey templates**

`operationId: JourneysController_listTemplates`

The starter templates (company-wide onboarding — the old six-item checklist plus the work others do —, contractor and offboarding) are seeded the first time.

#### Signature

```http
GET /business-made/journeys/templates (event?: string, status?: string, search?: string) -> `{ data:[bm_journey_template], total, page, pageSize }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `event` | query | string | — |  |
| `status` | query | string | — |  |
| `search` | query | string | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ data:[bm_journey_template], total, page, pageSize }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/templates

**Create a template**

`operationId: JourneysController_createTemplate`

Flat bm_journey_template body. Validated: title, event, unique task keys, known stages, dependsOn to known keys without loops, provisioning tasks name an action.

#### Signature

```http
POST /business-made/journeys/templates (body) -> bm_journey_template
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | template-invalid | task key "x" is used twice | The template does not validate. | Fix the listed errors. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

### Responses

| Status | Meaning |
| --- | --- |
| `201` | bm_journey_template |
| `400` | task key "x" is used twice — The template does not validate. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/journeys/templates/{id}

**Get a template**

`operationId: JourneysController_getTemplate`

#### Signature

```http
GET /business-made/journeys/templates/{id} (id: string) -> bm_journey_template
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | template-not-found | Journey template not found | No template has that id. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Template id (bm_journey_template sk). |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | bm_journey_template |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Journey template not found — No template has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /business-made/journeys/templates/{id}

**Update a template**

`operationId: JourneysController_updateTemplate`

Flat body merged over the stored template. Changing an active template’s tasks bumps its version; open journeys pick it up on their next re-plan.

#### Signature

```http
PUT /business-made/journeys/templates/{id} (id: string, body) -> bm_journey_template
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | template-not-found | Journey template not found | No template has that id. | — |
| `409` | template-code-taken | Template code ONB-KITCHEN is already used by "Kitchen onboarding" | Another template has that code. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Template id (bm_journey_template sk). |

### Request body

### Responses

| Status | Meaning |
| --- | --- |
| `200` | bm_journey_template |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Journey template not found — No template has that id. |
| `409` | Template code ONB-KITCHEN is already used by "Kitchen onboarding" — Another template has that code. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /business-made/journeys/templates/{id}

**Delete a template**

`operationId: JourneysController_removeTemplate`

A template open journeys still use is retired instead of deleted.

#### Signature

```http
DELETE /business-made/journeys/templates/{id} (id: string) -> `{ id, deleted, retired? }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Template id (bm_journey_template sk). |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ id, deleted, retired? }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/templates/{id}/preview

**Preview a template for a person**

`operationId: JourneysController_previewTemplate`

#### Signature

```http
POST /business-made/journeys/templates/{id}/preview (id: string, body) -> `{ tasks:[TaskView with dueDate], matches, employee }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Template id (bm_journey_template sk). |

### Request body

### Responses

| Status | Meaning |
| --- | --- |
| `201` | `{ tasks:[TaskView with dueDate], matches, employee }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/journeys/my-tasks

**My journey tasks**

`operationId: JourneysController_myTasks`

Tasks assigned to the caller across every journey (as the hire, manager, HR, IT, buddy, payroll…), overdue first.

#### Signature

```http
GET /business-made/journeys/my-tasks (status?: string) -> `{ data:[TaskView & { journeyId, journeyTitle, event, employeeName }] }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `status` | query | string | — | `open` (default: todo, in_progress, blocked), `done`, `skipped`, `all`, or a comma list. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ data:[TaskView & { journeyId, journeyTitle, event, employeeName }] }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/journeys/team

**My team**

`operationId: JourneysController_team`

The caller’s reports (supervisor / direct reports): readiness, what blocks them, and their open journey.

#### Signature

```http
GET /business-made/journeys/team () -> `{ data:[{employeeId, name, position, location, readiness:{overall, byGate}, blocking:[{kind, title, status, dueDate}], journey:{id, event, counts, stageNow, startDate, atRisk, riskReasons}\|null}] }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ data:[{employeeId, name, position, location, readiness:{overall, byGate}, blocking:[{kind, title, status, dueDate}], journey:{id, event, counts, stageNow, startDate, atRisk, riskReasons}\|null}] }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/journeys/reports

**Onboarding reports**

`operationId: JourneysController_reports`

Offer-to-ready days (offer accepted → required day-1 tasks done), time to productive (start → journey complete), on-time task completion, never-started hires, overdue by owner — overall, per location and per template. HR or admin only.

#### Signature

```http
GET /business-made/journeys/reports (event?: string, location?: string, from?: string, to?: string) -> `{ totals:{journeys, complete, offerToReadyDays, timeToProductiveDays, onTimeRate, neverStarted}, byLocation:[…], byTemplate:[…], overdueByOwner:[{name, relation, overdue}], neverStarted:[…] }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `event` | query | string | — |  |
| `location` | query | string | — |  |
| `from` | query | string | — | Start date from. |
| `to` | query | string | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ totals:{journeys, complete, offerToReadyDays, timeToProductiveDays, onTimeRate, neverStarted}, byLocation:[…], byTemplate:[…], overdueByOwner:[{name, relation, overdue}], neverStarted:[…] }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/migrate-checklists

**Move old onboarding checklists onto journeys**

`operationId: JourneysController_migrate`

Every employee with an in-progress six-item checklist and no journey gets an onboarding journey; completed and skipped items carry over. The old /business-made/onboarding endpoints do this per person on first touch.

#### Signature

```http
POST /business-made/journeys/migrate-checklists () -> `{ migrated, failed:[{employeeId, message}] }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | `{ migrated, failed:[{employeeId, message}] }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/tasks/{taskId}/complete

**Complete a task**

`operationId: JourneysController_complete`

Tasks that close from evidence cannot be ticked: if the evidence exists they close, otherwise 409 `completes-from-evidence` (HR can override with a note). The body can carry the evidence itself, and the platform writes the real record:
- policy: `{datatype:"bm_policy", id, version}` → the bm_policy_acknowledgement (409 `policy-changed` if a newer version exists);
- upload: `{datatype:"file", documentType, files:[{path,url}], expiresAt}` → the bm_employee_document (certifications need an unexpired expiry);
- e-sign: `{datatype:"signature", typedName, image, consent:true}` → the signed bm_employee_document, with time, IP and user agent.
A requirement-linked task then records readiness evidence.

#### Signature

```http
POST /business-made/journeys/tasks/{taskId}/complete (taskId: string, body) -> The TaskView.
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `409` | task-blocked | Waiting for: … | A prerequisite is not done. | Finish the prerequisite first. |
| `403` | not-assignee | This task is not yours | Caller is not an assignee, HR or admin. | — |
| `404` | task-not-found | Journey task not found | No journey task has that id. | — |
| `400` | evidence-invalid | Say what was done — a note is required to mark an account task | The evidence in the body does not satisfy the task type. The body carries a `reason` naming the check: note-required, answer-required, answer-invalid, fields-required, files-required, signature-required, card-required, serial-required, expiry-required, items-required, returns-required, policy-required, policy-changed, document-expired, form-not-found, policy-not-found, accounts-pending (with `pending`), inventory-unavailable, location-required or status-invalid. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `taskId` | path | string | yes | Task id (task sk). |

### Request body

```json
{
  "evidence": {
    "datatype": "bm_policy",
    "id": "66f0c3a1e4b0a1b2c3d4e5f9",
    "version": 4
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The TaskView. |
| `400` | Say what was done — a note is required to mark an account task — The evidence in the body does not satisfy the task type. The body carries a `reason` naming the check: note-required, answer-required, answer-invalid, fields-required, files-required, signature-required, card-required, serial-required, expiry-required, items-required, returns-required, policy-required, policy-changed, document-expired, form-not-found, policy-not-found, accounts-pending (with `pending`), inventory-unavailable, location-required or status-invalid. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | This task is not yours — Caller is not an assignee, HR or admin. |
| `404` | Journey task not found — No journey task has that id. |
| `409` | Waiting for: … — A prerequisite is not done. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/tasks/{taskId}/skip

**Skip a task**

`operationId: JourneysController_skip`

Optional tasks: the assignee or HR. Required tasks: HR only.

#### Signature

```http
POST /business-made/journeys/tasks/{taskId}/skip (taskId: string, body) -> The TaskView.
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | task-not-found | Journey task not found | No journey task has that id. | — |
| `409` | task-closed | This task is already closed | The task is done, skipped or cancelled. | — |
| `403` | required-task | A required task can only be skipped by HR | The task is required and the caller is not HR or an admin. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `taskId` | path | string | yes | Task id (task sk). |

### Request body

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The TaskView. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | A required task can only be skipped by HR — The task is required and the caller is not HR or an admin. |
| `404` | Journey task not found — No journey task has that id. |
| `409` | This task is already closed — The task is done, skipped or cancelled. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/journeys

**The journeys board**

`operationId: JourneysController_list`

Every journey, at-risk first then by start date. At risk = start within 3 days with paperwork missing, overdue required tasks, or upcoming shifts the person is blocked from by readiness. HR or admin only.

#### Signature

```http
GET /business-made/journeys (event?: string, status?: string, location?: string, atRisk?: boolean, search?: string, page?: integer, pageSize?: integer) -> `{ data:[{id, employeeId, employeeName, event, status, startDate, location, stageNow, counts:{done,total,overdue,requiredDone,requiredTotal}, atRisk, riskReasons:[string], riskCodes:[string]}], total, page, pageSize }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `event` | query | string | — | onboarding, contractor, offboarding, role_change, transfer… (comma list). |
| `status` | query | string | — | planned, in_progress, complete, cancelled, or `open`. Default: all but cancelled. |
| `location` | query | string | — |  |
| `atRisk` | query | boolean | — |  |
| `search` | query | string | — |  |
| `page` | query | integer | — | 0-based. |
| `pageSize` | query | integer | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ data:[{id, employeeId, employeeName, event, status, startDate, location, stageNow, counts:{done,total,overdue,requiredDone,requiredTotal}, atRisk, riskReasons:[string], riskCodes:[string]}], total, page, pageSize }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys

**Start a journey**

`operationId: JourneysController_start`

Creates the journey and its tasks from the templates that match the person (or `templateIds`). Due dates count from the start date, or for offboarding/changes from `effectiveDate`. A task marked for the exact effective time (offboarding revoke) is queued as its own one-off job at that instant; the sweep (every 10 minutes by default, see readiness settings) moves everything else and escalates overdue tasks to the manager, then HR, after the days set in `journeyEscalation`. An onboarding for employment type `contract` becomes a contractor journey when a contractor template exists. HR or admin only.

#### Signature

```http
POST /business-made/journeys (body) -> The journey detail (same shape as GET /business-made/journeys/{id}).
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `409` | journey-exists | already has an open journey | An open journey for this person and event exists. | Open it, or cancel it first. |
| `422` | no-template | No active template matches | No active template for the event matches the person. | Activate or widen a template, or pass templateIds. |
| `400` | effective-date-required | An offboarding needs its effectiveDate | Offboarding without an end time. | Send effectiveDate. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

```json
{
  "employeeId": "EMP-1043",
  "event": "onboarding"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The journey detail (same shape as GET /business-made/journeys/{id}). |
| `400` | An offboarding needs its effectiveDate — Offboarding without an end time. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `409` | already has an open journey — An open journey for this person and event exists. |
| `422` | No active template matches — No active template for the event matches the person. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /business-made/journeys/{id}

**A journey with its stages and tasks**

`operationId: JourneysController_detail`

#### Signature

```http
GET /business-made/journeys/{id} (id: string) -> `{ journey:{…row, replans, templateIds, buddyId, source}, stages:[{stage, title, dueDate, status, tasks:[TaskView]}], stageNow, counts, atRisk, riskReasons, employee }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | journey-not-found | Journey not found | No journey has that id. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Journey id (bm_journey sk). |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ journey:{…row, replans, templateIds, buddyId, source}, stages:[{stage, title, dueDate, status, tasks:[TaskView]}], stageNow, counts, atRisk, riskReasons, employee }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Journey not found — No journey has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/{id}/replan

**Re-plan a journey**

`operationId: JourneysController_replan`

Re-matches templates against the person now, adds and removes tasks, moves due dates with the start/effective date and re-resolves who does each open task. Happens on its own when role, department, location, start date or employment type change. Exact-time tasks (offboarding revoke) get their queued one-off job moved to the new time. HR or admin only.

#### Signature

```http
POST /business-made/journeys/{id}/replan (id: string) -> The journey detail.
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |
| `404` | journey-not-found | Journey not found | No journey has that id. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Journey id (bm_journey sk). |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The journey detail. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `404` | Journey not found — No journey has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/{id}/cancel

**Cancel a journey**

`operationId: JourneysController_cancel`

Cancels the open tasks and removes their queued exact-time jobs.

#### Signature

```http
POST /business-made/journeys/{id}/cancel (id: string, body) -> The journey detail.
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |
| `404` | journey-not-found | Journey not found | No journey has that id. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Journey id (bm_journey sk). |

### Request body

```json
{
  "reason": "Offer withdrawn"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The journey detail. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `404` | Journey not found — No journey has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /business-made/journeys/{id}/unlock-preboarding

**Open pre-boarding by hand**

`operationId: JourneysController_unlock`

For templates whose pre-boarding opens manually, or to open it early. The hire can then sign in before day 1 and sees only their onboarding.

#### Signature

```http
POST /business-made/journeys/{id}/unlock-preboarding (id: string) -> The journey detail.
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `403` | hr-only | Only HR or an admin can do this | The caller is not HR or an admin. | — |
| `404` | journey-not-found | Journey not found | No journey has that id. | — |
| `400` | no-preboarding | Only a hire’s first journey has pre-boarding | The journey is not an onboarding. | — |

Plus the standard platform errors: `401`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Journey id (bm_journey sk). |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The journey detail. |
| `400` | Only a hire’s first journey has pre-boarding — The journey is not an onboarding. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Only HR or an admin can do this — The caller is not HR or an admin. |
| `404` | Journey not found — No journey has that id. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

