# Business Made · Recruitment

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /business-made/recruitment/board

**Hiring board**

`operationId: RecruitmentController_board`

Jobs with their pipeline counts (applicants, in pipeline, hired, by stage), the pipeline itself (stages `new` → `screening` → `interview` → `assessment` → `offer` → `hired`, newest applicants first), totals and the jobs open for new applicants. Rejected and withdrawn applicants are left out unless `closed=true`.

#### Signature

```http
GET /business-made/recruitment/board (status?: string, jobPostingId?: string, closed?: string) -> `{ totals:{openJobs, onHold, filled, inPipeline, hired, notMovingForward}, statusCounts, jobs:[{id, title, department, location, employmentType, pay, status, statusLabel, openings, hired, applicants, inPipeline, byStage, postedAt, closingDate}], stages:[{stage, label, count}], applicants, jobOptions }`
```

#### Access

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

#### Errors

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

#### See also

- `GET /business-made/recruitment/board/jobs/{id}`

### 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 | — | Job status to show, or `active` (open, on hold, draft). |
| `jobPostingId` | query | string | — | Only this job's applicants in the pipeline. |
| `closed` | query | string | — | `true` adds the rejected and withdrawn stages. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ totals:{openJobs, onHold, filled, inPipeline, hired, notMovingForward}, statusCounts, jobs:[{id, title, department, location, employmentType, pay, status, statusLabel, openings, hired, applicants, inPipeline, byStage, postedAt, closingDate}], stages:[{stage, label, count}], applicants, jobOptions }` |
| `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/recruitment/board/jobs

**Create or edit a job**

`operationId: RecruitmentController_saveJob`

Saves a job posting from the hiring screen — with `sk` (or `id`) it edits that job, otherwise it creates one, as a draft unless opened straight away. Returns the job detail.

#### Signature

```http
POST /business-made/recruitment/board/jobs (body) -> The job detail (as GET /business-made/recruitment/board/jobs/{id})
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | JOB_REQUIRED | Required: title, employmentType | A required field is missing; the body lists `fields`. | — |
| `404` | JOB_POSTING_NOT_FOUND | Job not found | Editing a job that does not exist. | — |

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
{
  "title": "Line cook",
  "employmentType": "full-time",
  "department": "Kitchen",
  "location": [
    "harbor-grill"
  ],
  "openings": 2,
  "salary": {
    "min": 18,
    "max": 22,
    "period": "hourly"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The job detail (as GET /business-made/recruitment/board/jobs/{id}) |
| `400` | Required: title, employmentType — A required field is missing; the body lists `fields`. |
| `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` | Job not found — Editing a job that does not exist. |
| `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/recruitment/board/jobs/{id}

**One job with its applicants**

`operationId: RecruitmentController_jobDetail`

The job's fields, labels, pay, openings and hires, the status moves allowed from where it is (`moves`), and its applicants.

#### Signature

```http
GET /business-made/recruitment/board/jobs/{id} (id: string) -> `{ id, data, title, status, statusLabel, location, pay, openings, hired, moves:[{to, label}], applicants, postedAt, closedReason }`
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | JOB_POSTING_NOT_FOUND | Job posting not found | No job posting has that id. | The body carries `code: "JOB_POSTING_NOT_FOUND"` and the 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 | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ id, data, title, status, statusLabel, location, pay, openings, hired, moves:[{to, label}], applicants, postedAt, closedReason }` |
| `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` | Job posting not found — No job posting 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/recruitment/board/jobs/{id}/status

**Move a job to another status**

`operationId: RecruitmentController_setJobStatus`

Only the moves allowed from where the job is: draft → open or cancelled; open → on hold, filled or closed; on hold → open or closed; filled / closed → open; cancelled → draft.

#### Signature

```http
POST /business-made/recruitment/board/jobs/{id}/status (id: string, body) -> The job detail
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | JOB_POSTING_NOT_FOUND | Job posting not found | No job posting has that id. | The body carries `code: "JOB_POSTING_NOT_FOUND"` and the id. |
| `400` | JOB_STATUS_MOVE | A Draft job cannot move to Filled | That move is not allowed from the job's status. | — |

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 | Record id. |

### Request body

```json
{
  "status": "on-hold",
  "reason": "Budget review"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The job detail |
| `400` | A Draft job cannot move to Filled — That move is not allowed from the job's status. |
| `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` | Job posting not found — No job posting 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/recruitment/board/applicants

**Add an applicant**

`operationId: RecruitmentController_addApplicant`

Puts a person into the pipeline at `new`, for a job or with no job yet. Returns the applicant detail.

#### Signature

```http
POST /business-made/recruitment/board/applicants (body) -> The applicant detail
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | APPLICANT_REQUIRED | Required: firstName, lastName, email | A required field is missing; the body lists `fields`. | — |

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
{
  "jobPostingId": "66f0c3a1e4b0a1b2c3d4e5f6",
  "personalInfo": {
    "firstName": "Luis",
    "lastName": "Ortega",
    "email": "luis@example.com"
  },
  "source": "referral"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The applicant detail |
| `400` | Required: firstName, lastName, email — A required field is missing; the body lists `fields`. |
| `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/recruitment/board/applicants/{id}

**One applicant**

`operationId: RecruitmentController_applicantDetail`

The person, the job and its status, the current stage, stage history, notes, rejection reason, availability and expected salary, the pipeline stages with which are reached, `canReopen`, and `hireWith` (what the employee form should prefill when hiring).

#### Signature

```http
GET /business-made/recruitment/board/applicants/{id} (id: string) -> The applicant detail
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: "APPLICANT_NOT_FOUND"` and the 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 | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The applicant detail |
| `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` | Applicant not found — No applicant 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/recruitment/board/applicants/{id}/action

**Move an applicant along**

`operationId: RecruitmentController_applicantAction`

One endpoint for every pipeline step: `stage` (move to `stage`; hiring is its own step), `reject` (with `reason`), `withdraw`, `reopen` (bring a rejected or withdrawn applicant back), `notes` (replace the notes). Returns the applicant detail.

#### Signature

```http
POST /business-made/recruitment/board/applicants/{id}/action (id: string, body) -> The applicant detail
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: "APPLICANT_NOT_FOUND"` and the id. |
| `400` | APPLICANT_STAGE | Pick a pipeline stage (hiring is its own step) | `stage` is not a pipeline stage, or is `hired`. | — |

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 | Record id. |

### Request body

```json
{
  "action": "stage",
  "stage": "interview"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The applicant detail |
| `400` | Pick a pipeline stage (hiring is its own step) — `stage` is not a pipeline stage, or is `hired`. |
| `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` | Applicant not found — No applicant 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/recruitment/board/applicants/{id}/hired

**Record a hire**

`operationId: RecruitmentController_markHired`

Links the applicant to the employee record the people form created, marks them hired, and counts the hire against the job — which becomes `filled` once every opening is taken. Returns the applicant detail.

#### Signature

```http
POST /business-made/recruitment/board/applicants/{id}/hired (id: string, body) -> The applicant detail
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: "APPLICANT_NOT_FOUND"` and the id. |
| `400` | EMPLOYEE_REQUIRED | Which employee record was created? | `employeeId` is missing. | — |

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 | Record id. |

### Request body

```json
{
  "employeeId": "66f0c3a1e4b0a1b2c3d4e5f7"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The applicant detail |
| `400` | Which employee record was created? — `employeeId` is missing. |
| `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` | Applicant not found — No applicant 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/recruitment/jobs

**List job postings**

`operationId: RecruitmentController_getJobPostings`

Job postings across the org, published or not.

#### Signature

```http
GET /business-made/recruitment/jobs () -> Job postings
```

#### Access

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

#### Errors

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

#### See also

- `GET /business-made/organization/positions/status/open`

### 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` | Job postings |
| `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/recruitment/jobs

**Create a job posting**

`operationId: RecruitmentController_createJobPosting`

Creates a posting as a draft. Link it to an open position so the vacancy and the advert stay connected.

#### Signature

```http
POST /business-made/recruitment/jobs (body) -> The created posting
```

#### Access

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

#### Errors

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

#### See also

- `POST /business-made/recruitment/jobs/{id}/publish`

### 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

The posting to create.

```json
{
  "title": "Senior Engineer",
  "positionId": "POS-eng-lead",
  "departmentId": "DEP-eng",
  "description": "Build and run the platform.",
  "location": "london"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created posting |
| `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/recruitment/jobs/{id}

**Get a job posting**

`operationId: RecruitmentController_getJobPosting`

Fetches one posting with its description and status.

#### Signature

```http
GET /business-made/recruitment/jobs/{id} (id: string) -> The posting
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |

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

#### See also

- `POST /business-made/recruitment/jobs/{id}/publish`

### 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 | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The posting |
| `400` | invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. |
| `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. |

## DELETE /business-made/recruitment/jobs/{id}

**Delete a job posting**

`operationId: RecruitmentController_deleteJobPosting`

Deletes a posting. Applicants attached to it are not removed, so close it instead where the pipeline matters.

#### Signature

```http
DELETE /business-made/recruitment/jobs/{id} (id: string) -> Deletion result
```

#### Access

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

#### Errors

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

#### See also

- `POST /business-made/recruitment/jobs/{id}/close`

### 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 | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Deletion result |
| `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/recruitment/jobs/update

**Update a job posting**

`operationId: RecruitmentController_updateJobPosting`

Updates a posting. Editing one already published changes what candidates see immediately. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.

#### Signature

```http
POST /business-made/recruitment/jobs/update (body) -> The updated posting
```

#### Access

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

#### Errors

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

#### See also

- `POST /business-made/recruitment/jobs/{id}/close`

### 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

The posting to update.

```json
{
  "sk": "JOB-4821",
  "data": {
    "title": "Staff Engineer"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated posting |
| `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/recruitment/jobs/{id}/publish

**Publish a job posting**

`operationId: RecruitmentController_publishJobPosting`

Makes a posting live and open to applications.

#### Signature

```http
POST /business-made/recruitment/jobs/{id}/publish (id: string) -> The published posting
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | JOB_POSTING_NOT_FOUND | Job posting not found | No job posting has that id. | The body carries `code: "JOB_POSTING_NOT_FOUND"` and the id. |

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

#### See also

- `POST /business-made/recruitment/jobs/{id}/close`

### 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 | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The published posting |
| `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` | Job posting not found — No job posting 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/recruitment/jobs/{id}/close

**Close a job posting**

`operationId: RecruitmentController_closeJobPosting`

Stops new applications while keeping the posting and its pipeline. Candidates already in process are unaffected.

#### Signature

```http
POST /business-made/recruitment/jobs/{id}/close (id: string, body) -> The closed posting
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | JOB_POSTING_NOT_FOUND | Job posting not found | No job posting has that id. | The body carries `code: "JOB_POSTING_NOT_FOUND"` and the id. |

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

#### See also

- `GET /business-made/recruitment/metrics/applicants-by-stage`

### 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 | Record id. |

### Request body

Optional detail.

```json
{
  "reason": "Position filled"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The closed posting |
| `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` | Job posting not found — No job posting 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/recruitment/applicants

**List applicants**

`operationId: RecruitmentController_getApplicants`

Applicants across the org, filterable by job and stage.

#### Signature

```http
GET /business-made/recruitment/applicants (jobId?: string, stage?: string) -> Applicants
```

#### Access

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

#### Notes

- Candidate data is personal data held about non-employees — retention limits usually apply.

#### Errors

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

#### See also

- `GET /business-made/recruitment/metrics/applicants-by-stage`

### 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. |
| `jobId` | query | string | — |  |
| `stage` | query | string | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Applicants |
| `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/recruitment/applicants

**Create an applicant**

`operationId: RecruitmentController_createApplicant`

Records an application against a posting.

#### Signature

```http
POST /business-made/recruitment/applicants (body) -> The created applicant
```

#### Access

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

#### Errors

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

#### See also

- `POST /business-made/recruitment/applicants/{id}/stage`

### 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

The applicant.

```json
{
  "jobId": "JOB-4821",
  "firstName": "Grace",
  "lastName": "Hopper",
  "email": "grace@example.com",
  "source": "careers-site"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created applicant |
| `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/recruitment/applicants/{id}

**Get an applicant**

`operationId: RecruitmentController_getApplicant`

Fetches one applicant with their stage, notes and interview history.

#### Signature

```http
GET /business-made/recruitment/applicants/{id} (id: string) -> The applicant
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |

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

#### See also

- `POST /business-made/recruitment/applicants/{id}/stage`

### 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 | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The applicant |
| `400` | invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. |
| `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/recruitment/applicants/update

**Update an applicant**

`operationId: RecruitmentController_updateApplicant`

Updates an applicant record. Stage changes and rejection have their own endpoints, which record the transition. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.

#### Signature

```http
POST /business-made/recruitment/applicants/update (body) -> The updated applicant
```

#### Access

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

#### Errors

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

#### See also

- `POST /business-made/recruitment/applicants/{id}/stage`

### 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

The applicant to update.

```json
{
  "sk": "APP-4821",
  "data": {
    "phone": "+15551234567"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated applicant |
| `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/recruitment/applicants/{id}/stage

**Move an applicant to a stage**

`operationId: RecruitmentController_moveApplicantStage`

Advances an applicant through the hiring pipeline. Each move is recorded, which is what makes time-in-stage measurable.

#### Signature

```http
POST /business-made/recruitment/applicants/{id}/stage (id: string, body) -> The updated applicant
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: "APPLICANT_NOT_FOUND"` and the id. |

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

#### See also

- `POST /business-made/recruitment/interviews`

### 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 | Record id. |

### Request body

The new stage.

```json
{
  "stage": "interview",
  "note": "Strong screening call"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated applicant |
| `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` | Applicant not found — No applicant 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/recruitment/applicants/{id}/reject

**Reject an applicant**

`operationId: RecruitmentController_rejectApplicant`

Records a rejection with a reason. Keeping the reason matters both for candidate feedback and for showing hiring decisions were made on consistent grounds.

#### Signature

```http
POST /business-made/recruitment/applicants/{id}/reject (id: string, body) -> The rejected applicant
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: "APPLICANT_NOT_FOUND"` and the id. |

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

#### See also

- `POST /business-made/recruitment/applicants/{id}/notes`

### 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 | Record id. |

### Request body

Why they were rejected.

```json
{
  "reason": "Insufficient experience with distributed systems",
  "notifyCandidate": true
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The rejected applicant |
| `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` | Applicant not found — No applicant 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/recruitment/applicants/{id}/notes

**Add a note to an applicant**

`operationId: RecruitmentController_addApplicantNote`

Adds an internal note. Assume a candidate could one day request their record — write notes about evidence and fit, not about the person.

#### Signature

```http
POST /business-made/recruitment/applicants/{id}/notes (id: string, body) -> The updated applicant
```

#### Access

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

#### Notes

- Subject-access requests can include recruitment notes.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | APPLICANT_NOT_FOUND | Applicant not found | No applicant has that id. | The body carries `code: "APPLICANT_NOT_FOUND"` and the id. |

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

#### See also

- `GET /business-made/recruitment/applicants/{id}`

### 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 | Record id. |

### Request body

The note.

```json
{
  "note": "Strong systems design; limited exposure to our stack"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated applicant |
| `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` | Applicant not found — No applicant 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/recruitment/interviews

**List interviews**

`operationId: RecruitmentController_getInterviews`

Scheduled and completed interviews.

#### Signature

```http
GET /business-made/recruitment/interviews (applicantId?: string) -> Interviews
```

#### Access

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

#### Errors

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

#### See also

- `POST /business-made/recruitment/interviews`

### 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. |
| `applicantId` | query | string | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Interviews |
| `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/recruitment/interviews

**Schedule an interview**

`operationId: RecruitmentController_scheduleInterview`

Schedules an interview for an applicant, with its panel and format.

#### Signature

```http
POST /business-made/recruitment/interviews (body) -> The scheduled interview
```

#### Access

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

#### Errors

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

#### See also

- `POST /business-made/recruitment/interviews/{id}/reschedule`

### 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

The interview to schedule.

```json
{
  "applicantId": "APP-4821",
  "scheduledAt": "2026-10-08T13:00:00.000Z",
  "type": "technical",
  "interviewers": [
    "EMP-4001"
  ]
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The scheduled interview |
| `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/recruitment/interviews/{id}

**Get an interview**

`operationId: RecruitmentController_getInterview`

Fetches one interview with its panel and feedback.

#### Signature

```http
GET /business-made/recruitment/interviews/{id} (id: string) -> The interview
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |

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

#### See also

- `POST /business-made/recruitment/interviews/{id}/complete`

### 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 | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The interview |
| `400` | invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. |
| `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/recruitment/interviews/update

**Update an interview**

`operationId: RecruitmentController_updateInterview`

Updates an interview. Use `reschedule` when the time changes — it records the move rather than silently overwriting it. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.

#### Signature

```http
POST /business-made/recruitment/interviews/update (body) -> The updated interview
```

#### Access

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

#### Errors

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

#### See also

- `POST /business-made/recruitment/interviews/{id}/reschedule`

### 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

The interview to update.

```json
{
  "sk": "INT-4821",
  "data": {
    "interviewers": [
      "EMP-4001",
      "EMP-4002"
    ]
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated interview |
| `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/recruitment/interviews/{id}/complete

**Complete an interview**

`operationId: RecruitmentController_completeInterview`

Records that an interview happened, with the panel's feedback and recommendation.

#### Signature

```http
POST /business-made/recruitment/interviews/{id}/complete (id: string, body) -> The completed interview
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | INTERVIEW_NOT_FOUND | Interview not found | No interview has that id. | The body carries `code: "INTERVIEW_NOT_FOUND"` and the id. |

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

#### See also

- `POST /business-made/recruitment/applicants/{id}/stage`

### 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 | Record id. |

### Request body

The outcome.

```json
{
  "recommendation": "advance",
  "feedback": "Strong design discussion; would hire"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The completed interview |
| `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` | Interview not found — No interview 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/recruitment/interviews/{id}/cancel

**Cancel an interview**

`operationId: RecruitmentController_cancelInterview`

Cancels a scheduled interview, keeping the record and the reason.

#### Signature

```http
POST /business-made/recruitment/interviews/{id}/cancel (id: string, body) -> The cancelled interview
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | INTERVIEW_NOT_FOUND | Interview not found | No interview has that id. | The body carries `code: "INTERVIEW_NOT_FOUND"` and the id. |

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

#### See also

- `POST /business-made/recruitment/interviews/{id}/reschedule`

### 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 | Record id. |

### Request body

Why it was cancelled.

```json
{
  "reason": "Candidate withdrew"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The cancelled interview |
| `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` | Interview not found — No interview 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/recruitment/interviews/{id}/reschedule

**Reschedule an interview**

`operationId: RecruitmentController_rescheduleInterview`

Moves an interview to a new time, recording that it was rescheduled rather than overwriting the original slot.

#### Signature

```http
POST /business-made/recruitment/interviews/{id}/reschedule (id: string, body) -> The rescheduled interview
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | INTERVIEW_NOT_FOUND | Interview not found | No interview has that id. | The body carries `code: "INTERVIEW_NOT_FOUND"` and the id. |

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

#### See also

- `POST /business-made/recruitment/interviews/{id}/cancel`

### 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 | Record id. |

### Request body

The new time.

```json
{
  "scheduledAt": "2026-10-10T13:00:00.000Z",
  "reason": "Panel conflict"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The rescheduled interview |
| `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` | Interview not found — No interview 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/recruitment/offers

**List offers**

`operationId: RecruitmentController_getOffers`

Offers made, at any stage.

#### Signature

```http
GET /business-made/recruitment/offers () -> Offers
```

#### Access

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

#### Errors

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

#### See also

- `GET /business-made/recruitment/offers/{id}`

### 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` | Offers |
| `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/recruitment/offers

**Create an offer**

`operationId: RecruitmentController_createOffer`

Drafts an offer for an applicant. It is not sent until `send`, so terms can be agreed internally first.

#### Signature

```http
POST /business-made/recruitment/offers (body) -> The drafted offer
```

#### Access

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

#### Errors

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

#### See also

- `POST /business-made/recruitment/offers/{id}/send`

### 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

The offer to draft.

```json
{
  "applicantId": "APP-4821",
  "salary": 88000,
  "startDate": "2026-11-03",
  "gradeId": "GRD-g7"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The drafted offer |
| `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/recruitment/offers/{id}

**Get an offer**

`operationId: RecruitmentController_getOffer`

Fetches one offer with its terms and current status.

#### Signature

```http
GET /business-made/recruitment/offers/{id} (id: string) -> The offer
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | INVALID_ID | invalid id <id> | The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. | — |

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

#### See also

- `POST /business-made/recruitment/offers/{id}/send`

### 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 | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The offer |
| `400` | invalid id <id> — The id is not a valid record id. A well-formed id that matches nothing returns an empty 200, not a 404. |
| `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/recruitment/offers/update

**Update an offer**

`operationId: RecruitmentController_updateOffer`

Updates a draft offer. Changing terms after sending means re-sending — the candidate has already seen the previous version. **Replaces the stored record** — send the whole record as read (`sk` plus `data`), not just the changed fields.

#### Signature

```http
POST /business-made/recruitment/offers/update (body) -> The updated offer
```

#### Access

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

#### Errors

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

#### See also

- `POST /business-made/recruitment/offers/{id}/send`

### 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

The offer to update.

```json
{
  "sk": "OFR-4821",
  "data": {
    "salary": 92000
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated offer |
| `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/recruitment/offers/{id}/send

**Send an offer**

`operationId: RecruitmentController_sendOffer`

Sends the offer to the candidate. This is the point the terms become a commitment they can accept, so confirm them first.

#### Signature

```http
POST /business-made/recruitment/offers/{id}/send (id: string) -> The sent offer
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | OFFER_NOT_FOUND | Offer not found | No offer has that id. | The body carries `code: "OFFER_NOT_FOUND"` and the id. |

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

#### See also

- `POST /business-made/recruitment/offers/{id}/accept`

### 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 | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The sent offer |
| `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` | Offer not found — No offer 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/recruitment/offers/{id}/accept

**Record an accepted offer**

`operationId: RecruitmentController_acceptOffer`

Records that the candidate accepted. The handover point to onboarding — create the employee and start their checklist next.

#### Signature

```http
POST /business-made/recruitment/offers/{id}/accept (id: string, body) -> The accepted offer
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | OFFER_NOT_FOUND | Offer not found | No offer has that id. | The body carries `code: "OFFER_NOT_FOUND"` and the id. |

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

#### See also

- `POST /business-made/employees`
- `POST /business-made/onboarding/{employeeId}`

### 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 | Record id. |

### Request body

Optional detail.

```json
{
  "acceptedDate": "2026-10-15",
  "confirmedStartDate": "2026-11-03"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The accepted offer |
| `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` | Offer not found — No offer 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/recruitment/offers/{id}/decline

**Record a declined offer**

`operationId: RecruitmentController_declineOffer`

Records that the candidate declined, with their reason where given. Decline reasons are the most useful signal about whether offers are competitive.

#### Signature

```http
POST /business-made/recruitment/offers/{id}/decline (id: string, body) -> The declined offer
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | OFFER_NOT_FOUND | Offer not found | No offer has that id. | The body carries `code: "OFFER_NOT_FOUND"` and the id. |

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

#### See also

- `GET /business-made/recruitment/metrics`

### 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 | Record id. |

### Request body

Why they declined.

```json
{
  "reason": "Accepted a higher offer elsewhere"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The declined offer |
| `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` | Offer not found — No offer 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/recruitment/offers/{id}/withdraw

**Withdraw an offer**

`operationId: RecruitmentController_withdrawOffer`

Withdraws an offer already sent — the employer-side counterpart to decline. Record the reason: withdrawing a made offer is a decision that gets scrutinised.

#### Signature

```http
POST /business-made/recruitment/offers/{id}/withdraw (id: string, body) -> The withdrawn offer
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | OFFER_NOT_FOUND | Offer not found | No offer has that id. | The body carries `code: "OFFER_NOT_FOUND"` and the id. |

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

#### See also

- `POST /business-made/recruitment/offers/{id}/decline`

### 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 | Record id. |

### Request body

Why it was withdrawn.

```json
{
  "reason": "Headcount frozen"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The withdrawn offer |
| `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` | Offer not found — No offer 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/recruitment/metrics

**Get recruitment metrics**

`operationId: RecruitmentController_getRecruitmentMetrics`

Hiring figures — time to hire, offer acceptance rate and source effectiveness.

#### Signature

```http
GET /business-made/recruitment/metrics () -> Recruitment metrics
```

#### Access

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

#### Errors

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

#### See also

- `GET /business-made/recruitment/metrics/applicants-by-stage`

### 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` | Recruitment metrics |
| `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/recruitment/metrics/applicants-by-stage

**Get applicants by stage**

`operationId: RecruitmentController_getApplicantsByStage`

Pipeline volume at each stage — where candidates accumulate, and therefore where the bottleneck is.

#### Signature

```http
GET /business-made/recruitment/metrics/applicants-by-stage () -> Applicants per stage
```

#### Access

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

#### Errors

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

#### See also

- `GET /business-made/recruitment/metrics`

### 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. |
| `jobPostingId` | query | string | yes |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Applicants per stage |
| `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. |

