# Edara API v3: Sales Persons

Read the [guide](../guide.md) first: authentication, paging, errors, rate limits and the shared `PagedResult`, `BatchResult` and `*Reference` shapes.

- `GET /v3/sales-persons`: Lists sales persons.
- `GET /v3/sales-persons/{id}`: Gets a sales person by id.
- `POST /v3/sales-persons`: Creates a sales person.
- `PUT /v3/sales-persons/{id}`: Updates a sales person by id.
- `PUT /v3/sales-persons/code/{code}`: Updates a sales person by code.
- `DELETE /v3/sales-persons/{id}`: Deletes a sales person by id.
- `DELETE /v3/sales-persons/code/{code}`: Deletes a sales person by code.

## Endpoints

### `GET /v3/sales-persons`: Lists sales persons.

Operation `GetSalesPersons` · permission `read:sales-person`

**Query string**: `GetSalesPersonsQuery`

| field | type | default | validation | description |
|---|---|---|---|---|
| `offset` | `int?` | 0 | >= 0 | Number of items to skip before returning results. Defaults to 0. |
| `limit` | `int?` | 100 | 1..1000 (out of range → 400) | Maximum number of items to return. Defaults to 100, maximum 1000. |
| `code` | `string` |  |  | CONTAINS filter on code. |
| `externalId` | `string` |  |  | External id equality filter. |
| `name` | `string` |  |  | CONTAINS filter on name. |
| `updateDate` | `DateTime?` |  |  | Returns sales persons created or updated after this date. |
| _(object rule)_ | | | if (raw == null) { raw = HttpContext.Current?.Request?.QueryString?[queryKey] | |
| _(object rule)_ | | | if (raw == null) { return | |

**Responses**: OK `PagedResult<SalesPersonResponse>`: Paged list of sales persons.

**Notes**

- `code` and `name` match partially (contains), while `externalId` must match exactly.
- `updateDate` returns sales persons created or updated strictly after the value.
- Filter values are trimmed. A blank filter is ignored.
- The list includes internal and external sales persons, and inactive ones too.
- Results are ordered by id.
- When nothing matches, or the offset is past the end, the response is `404`, not an empty list. Treat that `404` as an empty result.

---

### `GET /v3/sales-persons/{id}`: Gets a sales person by id.

Operation `GetSalesPersonById` · permission `read:sales-person`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `id` | route | `int` |  | The sales person id. |

**Responses**: OK `SalesPersonResponse`: The sales person.

**Notes**

- Only internal sales persons are returned. An external sales person that the list endpoint returns gives `404` here, the same as an id that does not exist.
- Inactive sales persons are returned.

---

### `POST /v3/sales-persons`: Creates a sales person.

Operation `CreateSalesPerson` · permission `create:sales-person`

**Body**: `SalesPersonUpsertRequest`

| field | type | default | validation | description |
|---|---|---|---|---|
| `code` | `string` |  | MaximumLength(128) .Must(code => !string.IsNullOrWhiteSpace(code)) .When(x => x.Code != null) | Unique code. |
| `name` | `string` |  | NotEmpty() .MaximumLength(100) | Display name. |
| `classificationCode` | `int?` |  |  | Classification/category code. |
| `creditLimit` | `decimal` |  |  | Maximum credit limit the sales person can authorize. |
| `externalId` | `string` |  | MaximumLength(128) .Must(externalId => !string.IsNullOrWhiteSpace(externalId)) .When(x => x.ExternalId != null) | External system identifier. |

**Responses**: Created `SalesPersonResponse`: Sales person created.

**Business errors** (HTTP 409, match on `errorCode`):

- `DupplicatedCode`: Another sales person already uses the same code.
- `DupplicatedName`: Another sales person already uses the same name.

**Notes**

- `code` and `externalId` are optional, but if you send them they cannot be blank.
- A request without a body returns `400`.
- If you omit `code`, the sales person is saved without a code. No code is generated, and any number of sales persons can have no code.
- A new sales person is always active and always internal. The supervisor flag, the external flag and `tags` are ignored on create and get their default values.
- A duplicate `name` returns `409` with `errorCode` `DupplicatedName`. A duplicate `code` returns `409` with `errorCode` `DupplicatedCode`.
- Special characters are removed from `code` and `name` when they are saved. The `201` response repeats what you sent, so it can show characters that were not stored.

---

### `PUT /v3/sales-persons/{id}`: Updates a sales person by id.

Operation `UpdateSalesPersonById` · permission `update:sales-person`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `id` | route | `int` |  | The sales person id. |

**Body**: `SalesPersonUpsertRequest`

| field | type | default | validation | description |
|---|---|---|---|---|
| `code` | `string` |  | MaximumLength(128) .Must(code => !string.IsNullOrWhiteSpace(code)) .When(x => x.Code != null) | Unique code. |
| `name` | `string` |  | NotEmpty() .MaximumLength(100) | Display name. |
| `classificationCode` | `int?` |  |  | Classification/category code. |
| `creditLimit` | `decimal` |  |  | Maximum credit limit the sales person can authorize. |
| `externalId` | `string` |  | MaximumLength(128) .Must(externalId => !string.IsNullOrWhiteSpace(externalId)) .When(x => x.ExternalId != null) | External system identifier. |

**Responses**: OK `SalesPersonResponse`: The updated sales person.

**Business errors** (HTTP 409, match on `errorCode`):

- `DupplicatedCode`: Another sales person already uses the same code.
- `DupplicatedName`: Another sales person already uses the same name.

**Notes**

- Only internal sales persons can be updated. The id of an external sales person returns `404`.
- This is a full replace for `name`, `classificationCode` and `creditLimit`. If you omit `classificationCode` it is cleared, and if you omit `creditLimit` it becomes 0.
- `code` is the exception: if you omit it or send it empty, the stored code is kept.
- `externalId` in the body is ignored without an error, so you cannot change it with this endpoint. `isActive`, the supervisor flag and `tags` also keep their stored values.
- A duplicate `name` or `code` returns `409` with `errorCode` `DupplicatedName` or `DupplicatedCode`.

---

### `PUT /v3/sales-persons/code/{code}`: Updates a sales person by code.

Operation `UpdateSalesPersonByCode` · permission `update:sales-person`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `code` | route | `string` |  | The sales person code. |

**Body**: `SalesPersonUpsertRequest`

| field | type | default | validation | description |
|---|---|---|---|---|
| `code` | `string` |  | MaximumLength(128) .Must(code => !string.IsNullOrWhiteSpace(code)) .When(x => x.Code != null) | Unique code. |
| `name` | `string` |  | NotEmpty() .MaximumLength(100) | Display name. |
| `classificationCode` | `int?` |  |  | Classification/category code. |
| `creditLimit` | `decimal` |  |  | Maximum credit limit the sales person can authorize. |
| `externalId` | `string` |  | MaximumLength(128) .Must(externalId => !string.IsNullOrWhiteSpace(externalId)) .When(x => x.ExternalId != null) | External system identifier. |

**Responses**: OK `SalesPersonResponse`: The updated sales person.

**Business errors** (HTTP 409, match on `errorCode`):

- `DupplicatedCode`: Another sales person already uses the same code.
- `DupplicatedName`: Another sales person already uses the same name.

**Notes**

- The `code` in the path is trimmed and matched exactly, among internal sales persons only. No match returns `404`.
- Apart from the lookup, this behaves like the update by id: it is a full replace for `name`, `classificationCode` and `creditLimit`, and `externalId` in the body is ignored.
- If the body `code` is omitted or empty, the sales person keeps the code from the path. A different body `code` renames the sales person.

---

### `DELETE /v3/sales-persons/{id}`: Deletes a sales person by id.

Operation `DeleteSalesPersonById` · permission `delete:sales-person`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `id` | route | `int` |  | The sales person id. |

**Responses**: NoContent: (not declared; read from the method body)

**Business errors** (HTTP 409, match on `errorCode`):

- `ItemCannotDeleteItInUse`: The sales person is referenced by existing documents or related records.

**Notes**

- The delete is permanent, and the sales person's supervision links are removed with it. Success returns `204`.
- Only internal sales persons can be deleted. The id of an external sales person returns `404`.
- A sales person that is still referenced cannot be deleted and returns `409` with `errorCode` `ItemCannotDeleteItInUse`.
- References that block the delete include sales documents, quotes, targets, users linked to the sales person, work orders, physical counts, customer assignments and incentive assignments.

---

### `DELETE /v3/sales-persons/code/{code}`: Deletes a sales person by code.

Operation `DeleteSalesPersonByCode` · permission `delete:sales-person`

**Parameters**

| name | in | type | default | description |
|---|---|---|---|---|
| `code` | route | `string` |  | The sales person code. |

**Responses**: NoContent: (not declared; read from the method body)

**Business errors** (HTTP 409, match on `errorCode`):

- `ItemCannotDeleteItInUse`: The sales person is referenced by existing documents or related records.

**Notes**

- The `code` in the path is trimmed and matched exactly, among internal sales persons only. No match returns `404`.
- Apart from the lookup, this behaves like the delete by id. The delete is permanent, and a sales person that is still referenced returns `409` with `errorCode` `ItemCannotDeleteItInUse`.

## Types

### `SalesPersonResponse`

Response describing a sales person.

| field | type | description |
|---|---|---|
| `id` | `int` | Identifier. |
| `code` | `string` | Unique code. |
| `name` | `string` | Display name. |
| `classificationCode` | `int?` | Classification/category code. |
| `creditLimit` | `decimal` | Maximum credit limit the sales person can authorize. |
| `externalId` | `string` | External system identifier. |
