# Client Onboarding

**Onboard a new client with inline contact creation**
Creates a new client record and simultaneously resolves or creates up to three
associated contacts (preferred, billing, and secondary). Each contact field accepts
either an existing contact ID or a full inline payload to create a brand-new contact.
**Required fields:**
- `name` — client display name
- `pmsRef` — practice management system reference
- `clientRef` — unique client reference identifier
- `officeId` — ID of the office this client belongs to

**Contact fields** (`prefContact`, `billingContact`, `secondaryContact`):
Each contact field is optional but, when provided, must include **either** an existing
`id` **or** a `contactInfo` object — not both.
- When supplying `contactInfo`, `pmsRef` on the contact payload is required.
- At least one of `prefix`, `firstName`, or `lastName` must be provided inside
`contactInfo` to form a valid full name.

**Classification & billing fields** (all optional):
- `clientTypeId`, `clientBusinessEntityId` (entity type), and `staffBillingManagerId`
each reference an existing record; an unknown ID is rejected with **400**.
- `fiscalYearEndMonth` (1–12) and `fiscalYearEndDay` (1–31) capture the client's fiscal
year end.

**Custom fields** (`customFieldValuesJson`): a JSON object, encoded as a string, mapping
custom field keys to values. Keys not defined in the tenant's `CLIENT` custom-field schema
are dropped rather than failing the request, and are echoed back in
`data.ignoredCustomFields` (and appended to the response `message`). The onboard still
returns **201**.
**Tax regions** (`taxRegionIds`): a list of existing tax region IDs to link to the new
client. Duplicate IDs are de-duplicated; an unknown ID is rejected with **400**. The
assigned IDs are returned in `data.taxRegionIds`.
**Addresses** (`address`, `billingAddress`), both optional and stored the same way the
Firm Portal "Create New Client" dialog stores them:
- `address` is the client's own address. It is saved as the client's contact info record
and shows in the portal's *Address* section; its ID is returned in
`data.clientContactInfoId`.
- `billingAddress` is saved as a client address of type *Billing* and shows in the
portal's *Billing Address* section; its ID is returned in `data.billingAddressId`.
- `country` must be an ISO 3166-1 alpha-2 code (e.g. `US`, `CA`, `GB`); it is accepted
case-insensitively and stored upper-case.
- When `country` is `US`, `state` must be a US state name or abbreviation and is stored
as the abbreviation (e.g. `North Carolina` → `NC`). For other countries `state` is
stored as given.
- `pmsRef` is optional on both; when omitted an Aiwyn reference (`AIWYN_<uuid>`) is
generated. A `pmsRef` that already exists is rejected with **400**.
- An address object must carry at least one address field; an invalid country, state or
duplicate `pmsRef` rejects the whole request with **400** and no client is created.

**Real Example:**

```bash
curl --location 'https://{{host}}/api/v1/clients/v1/onboard' \
--header 'Authorization: Bearer {{token}}' \
--header 'Content-Type: application/json' \
--data '{
  "name": "Acme Corporation",
  "pmsRef": "ACME-001",
  "clientRef": "ACME-001",
  "officeId": 1,
  "prefContact": {
    "pmsRef": "CONT-001",
    "contactInfo": {
      "pmsRef": "CONT-001",
      "firstName": "Jane",
      "lastName": "Smith",
      "email": "jane.smith@acme.com"
    }
  },
  "address": {
    "address": "123 Main St",
    "address2": "Suite 400",
    "city": "Charlotte",
    "state": "NC",
    "country": "US",
    "zipCode": "28202"
  },
  "billingAddress": {
    "pmsRef": "ACME-001-BILLING",
    "address": "PO Box 999",
    "city": "Raleigh",
    "state": "NC",
    "country": "US",
    "zipCode": "27601"
  }
}'
```

Endpoint: POST /api/v1/clients/v1/onboard
Version: 1.0.0
Security: BearerAuth

## Request fields (application/json):

  - `name` (string, required)
    Client display name
    Example: Acme Corporation

  - `pmsRef` (string, required)
    Practice management system reference identifier
    Example: ACME-001

  - `clientRef` (string, required)
    Unique client reference identifier
    Example: ACME-001

  - `officeId` (integer, required)
    ID of the office this client belongs to
    Example: 1

  - `description` (string)
    Optional client description
    Example: A multinational technology company

  - `sortName` (string)
    Alternate name used for alphabetical sorting
    Example: Acme

  - `notes` (string)
    Internal notes about the client
    Example: Preferred billing on the 1st of the month

  - `departmentId` (integer)
    ID of the department associated with this client
    Example: 10

  - `orgId` (integer)
    ID of the organization associated with this client
    Example: 5

  - `industryId` (integer)
    ID of the industry classification for this client
    Example: 3

  - `partnerStaffId` (integer)
    ID of the partner staff member assigned to this client
    Example: 53

  - `managerStaffId` (integer)
    ID of the manager staff member assigned to this client
    Example: 15

  - `clientTypeId` (integer)
    ID of the client type classification
    Example: 7

  - `clientBusinessEntityId` (integer)
    ID of the client business entity (entity type)
    Example: 4

  - `staffBillingManagerId` (integer)
    ID of the staff member acting as billing manager for this client
    Example: 22

  - `fiscalYearEndMonth` (integer)
    Month (1-12) the client's fiscal year ends
    Example: 12

  - `fiscalYearEndDay` (integer)
    Day of month (1-31) the client's fiscal year ends
    Example: 31

  - `customFieldValuesJson` (string)
    JSON object, encoded as a string, mapping custom field keys to values. Keys not defined in the tenant's CLIENT custom-field schema are ignored and reported back in `data.ignoredCustomFields`.
    Example: {"industry_niche":"Manufacturing","naics_code":"333120"}

  - `taxRegionIds` (array)
    IDs of existing tax regions to assign to the new client. Duplicates are de-duplicated; an unknown ID returns 400.
    Example: [3,7]

  - `ebilling` (boolean)
    Whether this client is enrolled in electronic billing
    Example: true

  - `invoiceDeliveryMethod` (string)
    How invoices are delivered to the client. Defaults to EMAIL when `ebilling` is true, otherwise DOWNLOAD.
    Enum: "EMAIL", "DOWNLOAD", "EMAIL_AND_DOWNLOAD", "POST_ONLY"

  - `emailStatement` (boolean)
    Whether statements are emailed to the client
    Example: false

  - `chargeAdminFee` (boolean)
    Whether an administrative fee is charged to this client
    Example: false

  - `active` (boolean)
    Whether the client is active
    Example: true

  - `address` (object)
    The client's own address, stored as the client's contact info record (the portal's "Address" section). At least one address field is required; `pmsRef` is optional and generated as `AIWYN_<uuid>` when omitted.

  - `address.pmsRef` (string)
    PMS reference for the address record. Must not already exist.
    Example: ACME-001-ADDRESS

  - `address.address` (string)
    Example: 123 Main St

  - `address.address2` (string)
    Example: Suite 400

  - `address.city` (string)
    Example: Charlotte

  - `address.state` (string)
    For `country: US` a US state name or abbreviation, stored as the abbreviation. Free text for other countries.
    Example: NC

  - `address.country` (string)
    ISO 3166-1 alpha-2 country code; stored upper-case.
    Example: US

  - `address.zipCode` (string)
    Example: 28202

  - `billingAddress` (object)
    Billing address, stored as a client address of type Billing (the portal's "Billing Address" section). Same shape and rules as `address`.

  - `billingAddress.pmsRef` (string)
    PMS reference for the billing address record. Must not already exist.
    Example: ACME-001-BILLING

  - `billingAddress.address` (string)
    Example: PO Box 999

  - `billingAddress.address2` (string)
    Example: null

  - `billingAddress.city` (string)
    Example: Raleigh

  - `billingAddress.state` (string)
    Example: NC

  - `billingAddress.country` (string)
    Example: US

  - `billingAddress.zipCode` (string)
    Example: 27601

  - `prefContact` (object)
    Preferred contact. Provide either `id` (existing contact) or `contactInfo` (create new) — not both. `pmsRef` is required when creating a new contact.

  - `prefContact.id` (integer)
    ID of an existing contact to link
    Example: 501

  - `prefContact.username` (string)
    Email address used as the contact's login username
    Example: jane.smith@acme.com

  - `prefContact.pmsRef` (string)
    PMS reference for the contact. Required when creating a new contact.
    Example: CONT-001

  - `prefContact.type` (string)
    Contact type classification
    Example: PRIMARY

  - `prefContact.staffId` (integer)
    ID of the staff member associated with this contact
    Example: 53

  - `prefContact.officeId` (integer)
    ID of the office associated with this contact
    Example: 1

  - `prefContact.contactInfo` (object)
    Full contact details for creating a new contact record

  - `prefContact.contactInfo.pmsRef` (string, required)
    PMS reference for the contact info record
    Example: CONT-001

  - `prefContact.contactInfo.shortName` (string)
    Example: Jane S.

  - `prefContact.contactInfo.firstName` (string)
    Example: Jane

  - `prefContact.contactInfo.lastName` (string)
    Example: Smith

  - `prefContact.contactInfo.jobTitle` (string)
    Example: CFO

  - `prefContact.contactInfo.prefix` (string)
    Example: Ms.

  - `prefContact.contactInfo.salutation` (string)
    Example: Dear Jane

  - `prefContact.contactInfo.phone` (string)
    Example: +1-555-0123

  - `prefContact.contactInfo.email` (string)
    Example: jane.smith@acme.com

  - `prefContact.contactInfo.address` (string)
    Example: 123 Main St

  - `prefContact.contactInfo.address2` (string)
    Example: Suite 400

  - `prefContact.contactInfo.city` (string)
    Example: New York

  - `prefContact.contactInfo.state` (string)
    Example: NY

  - `prefContact.contactInfo.country` (string)
    Example: US

  - `prefContact.contactInfo.zipCode` (string)
    Example: 10001

  - `billingContact` (object)
    Billing contact. Same shape as `prefContact` — provide either `id` or `contactInfo`.

  - `billingContact.id` (integer)
    Example: 502

  - `billingContact.username` (string)
    Example: billing@acme.com

  - `billingContact.pmsRef` (string)
    Example: CONT-002

  - `billingContact.type` (string)
    Example: BILLING

  - `billingContact.staffId` (integer)
    Example: null

  - `billingContact.officeId` (integer)
    Example: 1

  - `billingContact.contactInfo` (object)

  - `billingContact.contactInfo.pmsRef` (string, required)
    Example: CONT-002

  - `billingContact.contactInfo.shortName` (string)
    Example: null

  - `billingContact.contactInfo.firstName` (string)
    Example: Bob

  - `billingContact.contactInfo.lastName` (string)
    Example: Finance

  - `billingContact.contactInfo.jobTitle` (string)
    Example: Accounts Payable

  - `billingContact.contactInfo.prefix` (string)
    Example: null

  - `billingContact.contactInfo.salutation` (string)
    Example: null

  - `billingContact.contactInfo.phone` (string)
    Example: +1-555-0124

  - `billingContact.contactInfo.email` (string)
    Example: billing@acme.com

  - `billingContact.contactInfo.address` (string)
    Example: null

  - `billingContact.contactInfo.address2` (string)
    Example: null

  - `billingContact.contactInfo.city` (string)
    Example: null

  - `billingContact.contactInfo.state` (string)
    Example: null

  - `billingContact.contactInfo.country` (string)
    Example: null

  - `billingContact.contactInfo.zipCode` (string)
    Example: null

  - `secondaryContact` (object)
    Secondary contact. Same shape as `prefContact` — provide either `id` or `contactInfo`.

  - `secondaryContact.id` (integer)
    Example: 503

  - `secondaryContact.username` (string)
    Example: null

  - `secondaryContact.pmsRef` (string)
    Example: null

  - `secondaryContact.type` (string)
    Example: null

  - `secondaryContact.staffId` (integer)
    Example: null

  - `secondaryContact.officeId` (integer)
    Example: null

  - `secondaryContact.contactInfo` (object)

  - `secondaryContact.contactInfo.pmsRef` (string, required)
    Example: CONT-003

  - `secondaryContact.contactInfo.firstName` (string)
    Example: Carol

  - `secondaryContact.contactInfo.lastName` (string)
    Example: Secondary

  - `secondaryContact.contactInfo.email` (string)
    Example: carol@acme.com

## Response 201 fields (application/json):

  - `success` (boolean)
    Example: true

  - `message` (string)
    Success message. When custom field keys are dropped, this is suffixed with "; ignored unknown custom fields: `{keys}`".
    Example: Client onboarded successfully

  - `data` (object)

  - `data.client` (object)
    The newly created client record

  - `data.client.id` (integer)
    Unique identifier of the created client
    Example: 1001

  - `data.client.pmsRef` (string)
    Example: ACME-001

  - `data.client.clientRef` (string)
    Example: ACME-001

  - `data.client.name` (string)
    Example: Acme Corporation

  - `data.client.description` (string)
    Example: null

  - `data.client.sortName` (string)
    Example: null

  - `data.client.officeId` (integer)
    Example: 1

  - `data.client.departmentId` (integer)
    Example: null

  - `data.client.orgId` (integer)
    Example: null

  - `data.client.industryId` (integer)
    Example: null

  - `data.client.partnerStaffId` (integer)
    Example: 53

  - `data.client.managerStaffId` (integer)
    Example: 15

  - `data.client.clientTypeId` (integer)
    Example: 7

  - `data.client.clientBusinessEntityId` (integer)
    Example: 4

  - `data.client.staffBillingManagerId` (integer)
    Example: 22

  - `data.client.fiscalYearEndMonth` (integer)
    Example: 12

  - `data.client.fiscalYearEndDay` (integer)
    Example: 31

  - `data.client.customFieldValuesJson` (string)
    JSON object, encoded as a string, of persisted custom field values
    Example: {"industry_niche":"Manufacturing"}

  - `data.client.active` (boolean)
    Example: true

  - `data.client.status` (string)
    Example: ACTIVE

  - `data.client.version` (integer)
    Example: 1

  - `data.client.createdAt` (string)
    Example: 2024-03-15T10:00:00Z

  - `data.client.updatedAt` (string)
    Example: 2024-03-15T10:00:00Z

  - `data.prefContactId` (integer)
    ID of the resolved preferred contact
    Example: 601

  - `data.billingContactId` (integer)
    ID of the resolved billing contact
    Example: null

  - `data.secondaryContactId` (integer)
    ID of the resolved secondary contact
    Example: null

  - `data.clientContactInfoId` (integer)
    ID of the contact info record created from `address`; null when no `address` was supplied
    Example: 44489

  - `data.billingAddressId` (integer)
    ID of the Billing client address created from `billingAddress`; null when no `billingAddress` was supplied
    Example: 211

  - `data.taxRegionIds` (array)
    IDs of the tax regions assigned to the client during onboarding
    Example: [3,7]

  - `data.ignoredCustomFields` (array)
    Custom field keys that were provided but ignored because they are not defined in the tenant's CLIENT custom-field schema
    Example: []

