> For the complete documentation index, see [llms.txt](https://doc.wearepatchworks.com/product-documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://doc.wearepatchworks.com/product-documentation/connectors-and-instances/patchworks-connectors/epicor-prophet-21.md).

# Epicor Prophet 21

## Description

**Epicor Prophet 21** (P21) is a cloud-enabled ERP solution built for wholesale distributors, combining distribution-specific functionality with inventory, order management, and financials in one system.

### API documentation

API documentation for Epicor Prophet 21 (used to build this connector) is available via the P21 middleware site for your tenant - for example:

```
https://<your-p21-host>/docs/home.aspx
```

The middleware site is the working entry point for API discovery:

| Page                             | Path                 | Used for                                                                                           |
| -------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------- |
| API Reference                    | `/docs/home.aspx`    | Browse operations, examples, request/response schemas, and enumerated values.                      |
| Transaction API Service Explorer | via API Reference    | Browse available service names and inspect the transaction surface.                                |
| API Console                      | `/docs/console.aspx` | Create and manage **Authorized API Consumers** (consumer label, scope, access type, token expiry). |

{% hint style="info" %}
The middleware **API Reference** shows a broad API surface, however your tenant's consumer scope, object exposure, and service definitions control what an integration can actually call. Always validate against the target tenant before building production mappings.
{% endhint %}

### Authentication

When adding connector instances for **Epicor Prophet 21**, the following authentication methods are supported:

* Username & password authentication
* Consumer key authentication

Both methods authenticate against the same P21 token endpoint (`POST /api/security/token/v2`) and return a bearer token, which Patchworks manages automatically. The only difference is how the endpoint authenticates the caller:

| Connection model    | Request fields                                                                                 | Use when                                                                                                                |
| ------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Username & password | `UserName` and `Password` (`ExpiresSeconds` optional)                                          | A P21 user credential is available and the connection should run in that user's context.                                |
| Consumer key        | `ClientSecret`, `GrantType` (`client_credentials`), and `UserName` (`ExpiresSeconds` optional) | A service-style connection should use the generated secret from **Authorized API Consumers** instead of a P21 password. |

#### Username & password authentication

{% tabs %}
{% tab title="Required credentials" %}
When a user chooses to add a connector instance using **username & password** authentication, the credentials below are prompted:

| Credential   | Required? | Notes                                                                                                                        |
| ------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `URL`        | Yes       | The base URL of your P21 middleware host - for example: `https://mycompany.epicorsaas.com`                                   |
| `username`   | Yes       | A P21 user. The connection runs in this user's context, so ensure the user has the permissions needed for your integrations. |
| `password`   | Yes       | The password for the P21 user above.                                                                                         |
| {% endtab %} |           |                                                                                                                              |

{% tab title="Obtaining credentials" %}
**URL**

The URL is the host you use to access the P21 middleware site - everything up to (but not including) `/docs/home.aspx`. If in doubt, your Epicor administrator or partner can confirm the correct host for your tenant.

**Username & password**

We recommend creating a dedicated P21 user for Patchworks (e.g. named `patchworks`) rather than using a personal login. This keeps API activity clearly attributable and avoids disruption if a member of staff leaves or changes their password.
{% endtab %}

{% tab title="Token request example" %}
Patchworks handles token creation and refresh automatically, but for reference (or testing in Postman), the underlying call is:

```
POST {p21Base}/api/security/token/v2
Content-Type: application/json
Accept: application/json
```

```json
{
  "UserName": "<P21 username>",
  "Password": "<P21 password>",
  "ExpiresSeconds": 3600
}
```

A successful response contains the access token and its context:

```json
{
  "AccessToken": "<token>",
  "TokenType": "Bearer",
  "ExpiresIn": 3600,
  "Scope": "<scope returned by tenant>",
  "UserName": "<P21 username>"
}
```

All subsequent calls send the token in the `Authorization` header:

```
Authorization: Bearer <AccessToken>
Accept: application/json
```

{% endtab %}
{% endtabs %}

#### Consumer key authentication

{% tabs %}
{% tab title="Required credentials" %}
When a user chooses to add a connector instance using **consumer key** authentication, the credentials below are prompted:

| Credential      | Required? | Notes                                                                                      |
| --------------- | --------- | ------------------------------------------------------------------------------------------ |
| `URL`           | Yes       | The base URL of your P21 middleware host - for example: `https://mycompany.epicorsaas.com` |
| `username`      | Yes       | The P21 user associated with the API consumer.                                             |
| `client secret` | Yes       | The generated secret from an **Authorized API Consumer**, created in the P21 API Console.  |
| {% endtab %}    |           |                                                                                            |

{% tab title="Obtaining credentials" %}
**Client secret**

To obtain the client secret required for Patchworks to access your **Prophet 21** data, you must create an **Authorized API Consumer**. To do this, follow the steps below.

**Step 1**\
Log into your **P21 middleware site** and open the **API Console** (`/docs/console.aspx`).

**Step 2**\
Locate the **Authorized API Consumers** section.

**Step 3**\
Enter a descriptive **consumer label** - we suggest setting this to 'Patchworks'.

**Step 4**\
Enter the **API scope** needed for your integrations - see the **additional setup** tab for scope guidance.

**Step 5**\
Select the **service** consumer type (for a non-interactive connection) and select **SDK access** if your integrations will use the transaction (`/p21sdk`) surface.

**Step 6**\
Choose an appropriate **token expiry**.

**Step 7**\
Click **add consumer** and copy the generated secret - add this to your password manager, ready for use in your Patchworks setup.

{% hint style="warning" %}
The generated secret is only displayed once. If the value is lost, rotate or recreate the consumer - don't rely on recovering it from P21.
{% endhint %}
{% endtab %}

{% tab title="Token request example" %}
Patchworks handles token creation and refresh automatically, but for reference (or testing in Postman), the underlying call is:

```
POST {p21Base}/api/security/token/v2
Content-Type: application/json
Accept: application/json
```

```json
{
  "ClientSecret": "<generated consumer key>",
  "GrantType": "client_credentials",
  "UserName": "<P21 username>",
  "ExpiresSeconds": 3600
}
```

The response and `Authorization: Bearer <AccessToken>` header usage are identical to username & password authentication.

{% hint style="info" %}
The v2 token schema also exposes fields such as `AppKey`, `ConsumerKey`, and `Scope`. The two connection models above are the confirmed patterns for this connector - scope is normally configured on the consumer in the API Console rather than supplied in the token body.
{% endhint %}
{% endtab %}

{% tab title="Additional setup" %}
**Scopes**

The scope configured on your Authorized API Consumer controls which P21 API surfaces the connection can access:

| Target surface          | Scope example                          | Notes                                                              |
| ----------------------- | -------------------------------------- | ------------------------------------------------------------------ |
| Transaction API and SDK | `/api;/p21sdk`                         | Requires **SDK access** to be selected on the consumer.            |
| Data Services OData     | `/odata:<comma-separated object list>` | Confirm the specific table and view names exposed to the consumer. |
| Other API routes        | `/api`                                 | Confirm whether your target routes require this broader scope.     |

{% hint style="info" %}
Your tenant's consumer scope and object exposure determine what Patchworks can call - if requests fail with authorisation errors, check the consumer configuration in the API Console first.
{% endhint %}
{% endtab %}
{% endtabs %}

### Making API calls

Prophet 21 exposes several API surfaces. As a general rule: use the **Transaction API** for business writes, **OData** for reads and searches, and the **Entity** or **Inventory REST** routes only when their resource contracts fit the operation.

| Surface             | What it is                                                       | Best fit                                                                        | Base path                          |
| ------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------- |
| Transaction API     | Service and transaction operations that apply P21 business rules | Creates, updates, multi-row workflows, and operations that must behave like P21 | `{uiBase}/api/v2`                  |
| Data Services OData | OData access to exposed tables and views                         | Read and search operations, lightweight synchronisation, and reporting data     | `{p21Base}/odataservice/odata`     |
| Entity API          | Simple entity-oriented routes for selected resources             | Straightforward customer, vendor, contact, or address operations                | `{p21Base}/api/entity`             |
| Inventory REST API  | Direct item and inventory-resource operations                    | Create, read, update, availability, and pricing operations for items            | `{p21Base}/api/inventory/v2/parts` |

Where `{p21Base}` is your middleware host (as entered in the connector instance URL) and `{uiBase}` is the transaction host resolved via the router - see below.

#### Transaction API

The Transaction API exposes P21 service operations. Each service has a name, a definition, and a transaction payload. **The service definition is the source of truth** for data element names, key fields, field names, and valid values.

{% tabs %}
{% tab title="Resolve the base URL" %}
Transaction API calls use a UI base URL which may differ from the middleware host. Resolve it via the router:

```
GET {p21Base}/api/ui/router/v1/?urlType=external
```

Use the URL returned by this router as `{uiBase}` for all transaction calls. Don't assume the middleware host is the correct transaction host for every environment.
{% endtab %}

{% tab title="Discover services" %}

| Purpose        | Method and route                               | Result                                                                                   |
| -------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------- |
| List services  | `GET {uiBase}/api/v2/services`                 | Returns service names such as `Customer`, `Order`, `Vendor`, and other tenant services.  |
| Get definition | `GET {uiBase}/api/v2/definition/{ServiceName}` | Returns the data elements, keys, fields, and valid values needed to construct a request. |
| Get defaults   | `GET {uiBase}/api/v2/defaults/{ServiceName}`   | Returns service defaults where supported.                                                |
| Get basics     | `GET {uiBase}/api/v2/basics/{ServiceName}`     | Returns basic service metadata where supported.                                          |
| {% endtab %}   |                                                |                                                                                          |

{% tab title="Submit a transaction" %}

```
POST {uiBase}/api/v2/transaction
Content-Type: application/json
Authorization: Bearer <AccessToken>
```

```json
{
  "Name": "<exact service name>",
  "UseCodeValues": false,
  "Transactions": [
    {
      "Status": "New",
      "DataElements": [
        {
          "Name": "<exact data element from definition>",
          "Type": "Form",
          "Keys": [],
          "Rows": [
            {
              "Edits": [
                {"Name": "<exact field name>", "Value": "<value>"}
              ],
              "RelativeDateEdits": []
            }
          ]
        }
      ]
    }
  ]
}
```

{% hint style="warning" %}
This body is a **request shape**, not a universal service payload. Replace every placeholder with the names and structure returned by the selected service definition. A successful HTTP response does not by itself prove that the business transaction succeeded - inspect the transaction status and response details.
{% endhint %}
{% endtab %}

{% tab title="Read a transaction state" %}

```
POST {uiBase}/api/v2/transaction/get
Content-Type: application/json
Authorization: Bearer <AccessToken>
```

```json
{
  "ServiceName": "<exact service name>",
  "TransactionStates": [
    {
      "DataElementName": "<exact data element>",
      "Keys": [
        {
          "Name": "<key field>",
          "Value": "<key value>"
        }
      ]
    }
  ]
}
```

{% endtab %}
{% endtabs %}

**Example: create an item with the Item service**

The example below creates an item using the `Item` service. Retrieve the live `Item` definition first and confirm that the data element names, field names, and accepted values match the target tenant.

```
POST {uiBase}/api/v2/transaction
Content-Type: application/json
Authorization: Bearer <AccessToken>
```

```json
{
  "Name": "Item",
  "UseCodeValues": false,
  "Transactions": [
    {
      "Status": "New",
      "DataElements": [
        {
          "Name": "TABPAGE_1.tp_1_dw_1",
          "Type": "Form",
          "Rows": [
            {
              "Edits": [
                {"Name": "item_id", "Value": "5056854205639"},
                {"Name": "short_code", "Value": "Armorduct Pre-Galvanised Light"},
                {"Name": "extended_desc", "Value": "The Armorduct Pre-Galvanised Lighting Trunking Horizontal 90 Deg Bend has built-in integrated couplers so no need to order separate parts"}
              ]
            }
          ]
        },
        {
          "Name": "TABPAGE_2.tp_2_dw_2",
          "Type": "List",
          "Rows": [
            {
              "Edits": [
                {"Name": "unit_of_measure", "Value": "Unit"}
              ]
            }
          ]
        },
        {
          "Name": "TABPAGE_7.tp_7_dw_7",
          "Type": "Form",
          "Rows": [
            {
              "Edits": [
                {"Name": "supplier_name", "Value": "Armorduct Systems Ltd"},
                {"Name": "upc_code", "Value": "A2WC55"}
              ]
            }
          ]
        }
      ]
    }
  ]
}
```

{% hint style="warning" %}
**Verify the result.** Inspect the transaction response and status before treating the item as created, then retrieve the item through a read route (OData or the Inventory REST API) to confirm the fields that actually landed. Don't resubmit the create request blindly if the response is ambiguous - first check whether the `item_id` already exists.
{% endhint %}

**Example: read an item back**

```
POST {uiBase}/api/v2/transaction/get
Content-Type: application/json
Authorization: Bearer <AccessToken>
```

```json
{
  "ServiceName": "Item",
  "TransactionStates": [
    {
      "DataElementName": "TABPAGE_1.tp_1_dw_1",
      "Keys": [
        {
          "Name": "item_id",
          "Value": "5056854205639"
        }
      ]
    }
  ]
}
```

#### Data Services OData

Data Services exposes OData entity sets for tables **and** views that the tenant has made available. A view can join, calculate, filter, or reshape data, so a view with a similar name to a table should not be treated as an identical replacement.

Discover the exposed schema - the `$metadata` responses are the authoritative catalogue of entity sets and fields available to the current connection:

```
GET {p21Base}/odataservice/odata/table/$metadata
GET {p21Base}/odataservice/odata/view/$metadata
```

Query a table or view:

```
GET {p21Base}/odataservice/odata/table/<table_name>?$top=10
GET {p21Base}/odataservice/odata/view/<view_name>?$top=10
```

...and with server-side selection, filtering, ordering, and paging:

```
GET {p21Base}/odataservice/odata/view/<view_name>?$select=<field1>,<field2>&$filter=<field> eq '<value>'&$orderby=<field>&$top=50&$skip=0
```

{% hint style="info" %}
Use `$select` to request only the fields your integration needs, and `$filter`, `$orderby`, `$top`, and `$skip` for bounded, server-side reads. Treat OData as **read-oriented** - use the Transaction API, Entity API, or Inventory REST API for writes.
{% endhint %}

#### Entity API

The Entity API provides a narrower, entity-oriented interface for selected resources. It's useful for simple record operations, but shouldn't replace a transaction service when the operation requires P21 business rules, related records, or multi-step processing.

Customer route examples:

| Purpose             | Method and route                                              | Notes                                                    |
| ------------------- | ------------------------------------------------------------- | -------------------------------------------------------- |
| Health check        | `GET {p21Base}/api/entity/customers/ping`                     | Use as a first route test if enabled in the tenant.      |
| New record template | `GET {p21Base}/api/entity/customers/new`                      | Use the returned shape as the create or update template. |
| Get one             | `GET {p21Base}/api/entity/customers/<CompanyId>_<CustomerId>` | Use the composite identifier returned by the API.        |
| Search collection   | `GET {p21Base}/api/entity/customers/?$query=<query>`          | Entity query syntax differs from OData `$filter`.        |
| Create              | `POST {p21Base}/api/entity/customers`                         | Build the body from the current `/new` response.         |
| Update              | `PUT {p21Base}/api/entity/customers/<CompanyId>_<CustomerId>` | Use the exact entity key and current payload shape.      |

Equivalent route families may exist for vendors, contacts, and addresses - confirm the resource, field shape, and supported methods in the target tenant before committing to this API.

#### Inventory REST API

The API Reference lists item operations under `/api/inventory/v2/parts` - a separate, item-specific REST route family from the generic `/api/entity` resources. Use it when direct item CRUD, availability, or pricing operations are a better fit than a service transaction.

| Purpose        | Method and route                                | Notes                                                      |
| -------------- | ----------------------------------------------- | ---------------------------------------------------------- |
| Health check   | `GET {p21Base}/api/inventory/v2/parts/ping`     | Confirms that the item service is available.               |
| Get an item    | `GET {p21Base}/api/inventory/v2/parts/{ITEMID}` | Returns the item resource for the supplied item ID.        |
| Create an item | `POST {p21Base}/api/inventory/v2/parts`         | `CreatePart` - use the request schema for required fields. |
| Update an item | `PUT {p21Base}/api/inventory/v2/parts/{ITEMID}` | `UpdatePart` - use the current resource shape.             |

Example create request (retrieve the full schema from the `CreatePart` operation page before sending):

```
POST {p21Base}/api/inventory/v2/parts
Content-Type: application/json
Authorization: Bearer <AccessToken>
```

```json
{
  "ItemId": "5056854205639",
  "ItemDesc": "Armorduct Pre-Galvanised Light",
  "ShortCode": "Armorduct Pre-Galvanised Light",
  "ExtendedDesc": "The Armorduct Pre-Galvanised Lighting Trunking Horizontal 90 Deg Bend has built-in integrated couplers so no need to order separate parts",
  "DefaultSellingUnit": "Unit"
}
```

### First connection test

A short, low-risk discovery sequence for validating a new connection:

1. Create a token with the selected authentication method.
2. Resolve the transaction UI base URL through the router.
3. List services and retrieve one service definition.
4. Retrieve the OData table and view `$metadata` documents.
5. Call an Entity API `/new` route, or the Inventory REST `ping`/item route, if that resource is in scope.
6. Test a document metadata or generation request only after the report or form identifier is known.
