# B2B – Request New Onboarding API

This API allows a B2B client to start a new onboarding session for a specific user under a specific integration. The API returns a generated onboarding link that can be delivered to the end user. The API is authenticated using an **OAuth 2.0 Client Credentials** access token.

***

## Security Model

The B2B Request New Onboarding API uses the **OAuth 2.0 Client Credentials** grant type — a server-to-server authentication pattern intended for trusted backend systems. Unlike the Authorization Code flow, this grant does not involve a user-facing login step. Instead, your server authenticates directly using a `client_id` and `client_secret` to obtain an access token, which is then used to authorize API requests.

A **Client Credentials integration** is created automatically for organizations that have **Integration Ecosystem** enabled.

## Find the Client ID & Client Secret

To view the integration details in Dashboard:

1. In the left navigation, go to **Integrations**.
2. On the Custom tab, click **Default client credentials integration**.

![](https://files.readme.io/43d5d27ed45608601b2afd8b07965903f7811a8a0332cb006fce6048db0b168c-image.png)

3. Copy the Client ID to use in the [access token request](https://developer.incode.com/docs/null#step-1-obtain-an-access-token).
4. Click **Generate** to create the Client Secret. Copy it to use in the [access token request](https://developer.incode.com/docs/null#step-1-obtain-an-access-token). The secret is shown and copyable **only once**. Store it securely in a secrets manager or vault. If lost, you must generate a new one.

***

## Step 1: Obtain an Access Token

Before calling the B2B onboarding endpoint, your backend must obtain a Bearer token from the Incode authorization server using the Client Credentials grant.

**Endpoint**: `POST {auth-server-url}/oauth2/token`

**Environment URLs**

| Environment       | Auth Server URL                |
| ----------------- | ------------------------------ |
| Demo              | `https://auth.demo.incode.com` |
| SaaS / Production | `https://auth.incode.com`      |

**Request Parameters**

| Parameter       | Value                | Notes                                                                                                                            |
| --------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `grant_type`    | `client_credentials` |                                                                                                                                  |
| `client_id`     | `{client_id}`        | [Find the Client ID](https://developer.incode.com/docs/b2b-request-new-onboarding-api#find-the-client-id--client-secret)         |
| `client_secret` | `{client_secret}`    | [Generate the Client Secret](https://developer.incode.com/docs/b2b-request-new-onboarding-api#find-the-client-id--client-secret) |
| `scope`         | `openid`             |                                                                                                                                  |

**Example Request**

```
POST {auth-server-url}/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id={clientId}&client_secret={secret}&scope=openid
```

A successful response returns an `access_token` to be used as a Bearer token in Step 2.

***

## Step 2: Call the B2B Request New Onboarding Endpoint

Use the access token obtained in Step 1 to call the onboarding endpoint.

**Endpoint**: `POST {base-api-url}/omni/b2b/onboarding/request-new`

**Environment URLs**

| Environment       | Base API URL                       |
| ----------------- | ---------------------------------- |
| Demo              | `https://demo-api.incodesmile.com` |
| SaaS / Production | `https://saas-api.incodesmile.com` |

### Headers

* `Authorization: Bearer <access_token>`
* `Content-Type: application/json`
* `x-api-key: {api_key}`

### Request Body

<Table align={["left","left","left","left"]}>
  <thead>
    <tr>
      <th>
        Field
      </th>

      <th>
        Required
      </th>

      <th>
        Description
      </th>

      <th>
        Example
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        `integrationReference`
      </td>

      <td>
        ✅ Yes
      </td>

      <td>
        Unique identifier of the integration, as configured in Incode Dashboard.
      </td>

      <td>
        `"int_b2b_prod_001"`
      </td>
    </tr>

    <tr>
      <td>
        `loginHint`
      </td>

      <td>
        ✅ Yes
      </td>

      <td>
        User identifier (email, username, phone, etc.).

        Employee-based integrations:

        * An existing employee record with the provided `loginHint` must already exist.

        * If no matching employee is found, the request will fail.

        Candidate-based integrations:

        * If no existing candidate is found with the provided `loginHint`, a new candidate will be created automatically.
      </td>

      <td>
        `"john.doe@clientcompany.com"`
      </td>
    </tr>

    <tr>
      <td>
        `notification`
      </td>

      <td>
        ✅ Yes
      </td>

      <td>
        Notification configuration object.
      </td>

      <td>
        See below
      </td>
    </tr>

    <tr>
      <td>
        `notification.type`
      </td>

      <td>
        ✅ Yes
      </td>

      <td>
        Must be `SMS`, `EMAIL`, or `URL`.
      </td>

      <td>

      </td>
    </tr>

    <tr>
      <td>
        `notification.email`
      </td>

      <td>
        Conditional
      </td>

      <td>
        Required if `type` is `EMAIL`.
      </td>

      <td>
        `"john.doe@clientcompany.com"`
      </td>
    </tr>

    <tr>
      <td>
        `notification.phone`
      </td>

      <td>
        Conditional
      </td>

      <td>
        Required if `type` is `SMS`.
      </td>

      <td>
        `"+14155552671"`
      </td>
    </tr>

    <tr>
      <td>
        `applicantId`
      </td>

      <td>
        Optional
      </td>

      <td>
        This should only be provided when requesting onboarding to an existing candidate.
      </td>

      <td>
        `"app_987654321"`
      </td>
    </tr>

    <tr>
      <td>
        `name`
      </td>

      <td>
        Conditional
      </td>

      <td>
        Applicant full name. New candidates must have a name.
      </td>

      <td>
        `"John Doe"`
      </td>
    </tr>

    <tr>
      <td>
        `meetingLink`
      </td>

      <td>
        Optional
      </td>

      <td>
        Custom meeting or session URL.
      </td>

      <td>
        `"https://meet.company.com/abc"`
      </td>
    </tr>

    <tr>
      <td>
        `linkValidityInMinutes`
      </td>

      <td>
        Optional
      </td>

      <td>
        How long the onboarding link remains valid.

        If omitted, system default validity of 15 minutes applies. You can configure a different value in Dashboard at **Configuration** > **General** > **Onboarding link duration**.
      </td>

      <td>
        `60`
      </td>
    </tr>
  </tbody>
</Table>

See [Migrating from Workforce](https://developer.incode.com/docs/b2b-request-new-onboarding-api#migrating-from-workforce) for a table that maps field names used in Workforce to field names used here.

**Example Request Body**

```json
{
  "integrationReference": "int_b2b_prod_001",
  "loginHint": "john.doe@clientcompany.com",
  "meetingLink": "https://meet.clientcompany.com/session/abc123",
  "notification": {
    "type": "URL"
  }
}
```

***

## Step 3: Handle the Response

### Success Response

**HTTP Status:** `200 OK`

Returned when a new onboarding session is successfully created and the onboarding link is generated.

**Response Body Schema**

```json
{
  "url": "string"
}
```

**Example Response**

```json
{
  "url": "https://url/workflow/69a8a52d8cc9700f429f9565?uuid=e22199bf-246e-4761-99e1-873cef74fc52&url_uuid=a5fe50d9-5f8e-4cd1-bcd2-189fe08c845e"
}
```

Deliver this URL to the end user via your preferred channel (email, SMS, in-app redirect, etc.).

### Error Responses

**HTTP Status:** `400 Bad Request`

Returned when a required field is missing or has an invalid value.

**Response Body Schema**

```json
{
  "timestamp": {timestamp},
  "status": {status},
  "error": {error},
  "message": {message},
  "path": "/omni/b2b/onboarding/request-new"
}
```

**Example Error Responses**

Missing or invalid `loginHint`:

```json
{
  "timestamp": 1773781956561,
  "status": 400,
  "error": "Bad Request",
  "message": "Login hint be a non-empty string",
  "path": "/omni/b2b/onboarding/request-new"
}
```

Missing phone number when `notification.type` is `SMS`:

```json
{
  "timestamp": 1773781994221,
  "status": 400,
  "error": "Bad Request",
  "message": "Phone must be provided when notification type is SMS",
  "path": "/omni/b2b/onboarding/request-new"
}
```

New candidate missing a name:

```json
{
  "timestamp": 1773782210449,
  "status": 400,
  "error": "Bad Request",
  "message": "BadRequestException: New candidate must have name.",
  "path": "/omni/b2b/onboarding/request-new"
}
```

## Migrating from Workforce

The table below maps deprecated Workforce request parameters to their Omni equivalents.

| Workforce field           | Omni equivalent         | Notes                                                                                  |
| ------------------------- | ----------------------- | -------------------------------------------------------------------------------------- |
| `integrationId`           | `integrationReference`  | Value format has changed — retrieve the new reference from the Omni Dashboard          |
| `secret`                  | _(removed)_             | Authentication is now handled via OAuth 2.0 Bearer token; no per-request secret needed |
| `loginHint`               | `loginHint`             | Unchanged                                                                              |
| `loginHintType`           | _(removed)_             | No longer required; type is inferred automatically                                     |
| `correlationId`           | `externalCustomerId`    | Surfaced in webhook payloads as `externalCustomerId`                                   |
| `redirectUrl`             | _(removed)_             | Configure redirect behavior at the workflow level in the Omni Dashboard                |
| `validityMinutes`         | `linkValidityInMinutes` | Renamed; same semantics                                                                |
| `givenNames` + `lastName` | `name`                  | Combined into a single full name field                                                 |
