P21 API Selection Guide
Disclaimer: This is unofficial, community-created documentation for Epicor Prophet 21 APIs. It is not affiliated with, endorsed by, or supported by Epicor Software Corporation. All product names, trademarks, and registered trademarks are property of their respective owners. Use at your own risk.
Overview
Prophet 21 provides several APIs for external data access and manipulation. This guide helps you choose the right API for your use case.
Quick Decision Table
| Need | Best API | Why |
|---|---|---|
| Read data quickly | OData | Standard protocol, efficient queries |
| Bulk create records | Transaction API | Stateless, supports batching |
| Complex business workflows | Interactive API | Full business logic, validation |
| Simple CRUD (customers, vendors, contacts, addresses) | Entity API | Stateless, domain objects |
CRUD on inventory items and locations (inv_loc) |
Inventory REST API | Stateless GET → modify → PUT |
Write to user-defined tables (udt_*) |
UDT Service API | Stateless insert/update/delete; read via OData |
| Update existing records (keyed fields) | Transaction API | Status: "New" + keyed rows updates and upserts (verified) |
| Update records behind dialogs/prompts | Interactive API | Only API that can answer response windows |
| Handle response dialogs | Interactive API | Only API with dialog handling |
| Record labor hours to production orders | Transaction API | TimeEntry service, stateless |
| Bulk create production orders | Transaction API | Stateless, high-volume creation |
| Modify or complete production orders | Interactive API | Stateful workflow with validation |
API Comparison
| Feature | OData | Transaction | Interactive | Entity | Inventory REST | UDT Service |
|---|---|---|---|---|---|---|
| Read Data | Excellent | Limited | Good | Good (4 entities) | Good (items) | No (use OData) |
| Create Data | No | Excellent | Good | Good (4 entities) | Good (items) | Good (UDT rows) |
| Update Data | No | Good* | Excellent | Good (4 entities) | Good (items/locations) | Good (by row_uid) |
| Delete Data | No | No | Via UI | Via flag | No | Yes |
| Bulk Operations | Yes (read) | Yes | No | No | No | Yes (multi-row insert) |
| Business Logic | No | Partial | Full | No | Partial (validation) | No |
| Session Required | No | No | Yes | No | No | No |
| Stateful | No | No | Yes | No | No | No |
| Response Dialogs | N/A | N/A | Yes | N/A | N/A | N/A |
Transaction API updates use Status: "New" with keyed rows (there is no working "Existing" status — it returns HTTP 500). Keyed rows behave as an upsert*: update if the key matches, insert if it doesn't. Verified at scale (170+ line updates, 80+ line inserts on JobContractPricing). See Updating an Existing Contract. Flows that pop validation dialogs still need the Interactive API.
OData API
Best For
- Reporting and data exports
- Quick lookups and searches
- Dashboard data
- Data validation
- Any read-only operation
Characteristics
- Standard OData v3 protocol - familiar to most developers
- Read-only - cannot create, update, or delete
- No session management - simple request/response
- Efficient - supports filtering, pagination, field selection
- Direct table/view access - query any P21 table or view
Use When
- You only need to read data
- Performance is critical
- You need standard query capabilities ($filter, $select, $orderby)
- You want to minimize complexity
Don't Use When
- You need to create or modify data
- You need business logic validation
Example Use Cases
- Get supplier list for dropdown
- Search for products
- Export pricing data
- Validate customer exists
- Dashboard metrics
Transaction API
Best For
- Bulk record creation
- External system integration
- Automated data import
- High-volume operations
Characteristics
- Stateless - each request is independent
- Bulk operations - multiple records per request
- Metadata-driven - follows P21 window schemas
- Fast - 50-100x faster than Interactive API for creates
- Updates via
Status: "New"+ keys - keyed rows upsert (update if the key matches, insert if not); the"Existing"status is broken (HTTP 500)
Use When
- Creating many records at once
- Updating or inserting keyed fields/rows on existing records
- Building integrations from external systems
- Performance is critical
- You don't need complex validation feedback
Don't Use When
- The operation pops validation dialogs / response windows (use Interactive API)
- A sub-tab stays disabled until a parent row is selected and
IgnoreDisableddoesn't unlock it (use Interactive API) - You need field-by-field validation feedback
Known Issues
- Session Pool Contamination - intermittent failures with some windows
Status: "Existing"is broken - returns HTTP 500 NullReferenceException platform-wide; useStatus: "New"for updates (see Transaction API)- Prompts are auto-answered with the default - a DynaChange or validation prompt kills the affected line/record silently
- See Session Pool Troubleshooting
Example Use Cases
- Import price pages from spreadsheet
- Sync products from external catalog
- Bulk create purchase orders
- Automated data migration
- Record labor hours against production orders (TimeEntry service)
- Bulk create production orders
Interactive API
Best For
- Complex business workflows
- Record updates
- Operations requiring validation
- Handling response dialogs
Characteristics
- Stateful - maintains session between requests
- Full business logic - all P21 validation and rules
- Window-based - interacts with P21 windows
- Response dialogs - can handle pop-up confirmations
- Reliable updates - field-level control
Use When
- Operations may trigger dialogs (email, confirmations) that must be answered
- Updating records where the Transaction API path hits disabled tabs or prompts
- You need full P21 business validation
- Complex multi-step workflows
- You need to mimic user interaction
Don't Use When
- Simple reads (use OData - faster)
- Bulk creates (use Transaction API - faster)
- You don't need business logic
Performance Note
The Interactive API is slower than the Transaction API (~5s vs 0.05s per created record; a windowed edit typically takes ~5 round-trips). Prefer the Transaction API when a keyed update works, and fall back to the Interactive API when the flow needs real window logic or dialog answers.
Version Note
Some P21 servers only support v2 Interactive API endpoints. If you receive 404 errors on /api/ui/interactive/v1/* endpoints, use /api/ui/interactive/v2/* instead. The v2 endpoints have different payload formats - see Interactive API v1 vs v2.
Example Use Cases
- Update purchase order status
- Modify customer records
- Complex order entry
- Any operation with approval dialogs
- Manage production orders (open, modify, complete)
- Record labor hours via Time Entry window
Entity API
Best For
- Customer, vendor, contact, and address CRUD
- Quick single-record operations on supported entities
- B2B integrations needing domain object models
Characteristics
- Entity-based - works with P21 domain objects (not raw table rows)
- Simple CRUD - create, read, update via REST
- Stateless - no session management required
- Umbrella term - Epicor's "Entity API" covers the whole REST API, including the
/api/entity/,/api/inventory/, and/api/sales/endpoint families (see the Terminology section of the Entity API doc) /api/entity/scope - the/api/entity/endpoint family covers only 4 entities: customers, vendors, contacts, addresses- Composite keys - customers/vendors use
{CompanyId}_{Id}format - Address limitations - Address entity has no
/newtemplate and no PUT/update (read and create only)
Use When
- You need CRUD on customers, vendors, contacts, or addresses
- You want cleaner domain objects than raw OData table rows
- You prefer stateless REST over Interactive API session management
Don't Use When
- You need orders — those live in the same REST API at
/api/sales/orders(see Other REST Endpoint Families) - You need inventory items — use the Inventory REST API (
/api/inventory/parts) - You need invoices, POs, or other business objects (no endpoint family found)
- You need bulk operations (use Transaction API)
- You need full business logic validation (use Interactive API)
Example Use Cases
- Look up customer with address:
GET /api/entity/customers/ACME_10?extendedproperties=CustomerAddress - Search vendors:
GET /api/entity/vendors/?$query=startswith(VendorName, 'ABC') - Get contact details:
GET /api/entity/contacts/1
Inventory REST API
Best For
- Inventory item CRUD, including location-level (
inv_loc) data - Multi-company workflows (adding items to new companies/locations)
- Item availability and pricing lookups
Characteristics
- Stateless - no session management required
- Item-centric - single base path
/api/inventory/parts, keyed by ItemId - GET → modify → PUT pattern - fetch the full payload, change or append, PUT it back
inv_locaccess - read, append, and update location records via extended properties- Validation - P21 validates changed values (e.g., invalid ProductGroupId is rejected)
Use When
- You need to read item details including location-specific data
- You need to append or update
inv_loc/inventory_supplierrecords - You need to create items or add existing items to new companies
Don't Use When
- You only need read access (use OData - faster, no payload round-trip)
- You need bulk operations across many items (use Transaction API)
- The change triggers dialogs or complex window logic (use Interactive API)
Example Use Cases
- Read an item with locations:
GET /api/inventory/parts/WIDGET-001?extendedproperties=* - Add an item to a new company/location (GET → append → PUT)
- Update
inv_locfields like Sellable or ProductGroupId (GET → modify → PUT)
See Inventory REST API for full details.
UDT Service API
Best For
- Writing to user-defined tables (
udt_*) from external systems - Automating data entry for custom workflows built on UDTs
Characteristics
- Write-only - insert, update, and delete endpoints; read UDT data via OData
- Stateless - no session management required
- Column-based payloads - data sent as column name/value pairs
- Condition-based targeting - updates and deletes identify rows via
row_uid
Use When
- You need to insert, update, or delete rows in a
udt_*table - B2B integrations that write to custom P21 tables
Don't Use When
- You need to read UDT data (use OData - the UDT Service API has no read endpoints)
- You're working with standard P21 tables (use the other APIs)
Example Use Cases
- Insert rows:
POST /udtservice/api/udtdata/insertudtdata - Update rows by
row_uid:PUT /udtservice/api/udtdata/updateudtdata - Delete rows:
DELETE /udtservice/api/udtdata/deleteudtdata
See UDT Service API for full details.
Decision Flowchart
Start
│
├─ Need to READ data only?
│ │
│ └─ Yes → Use OData API
│
├─ Writing to a user-defined table (udt_*)?
│ │
│ └─ Yes → Use UDT Service API
│
├─ CRUD on inventory items / inv_loc?
│ │
│ └─ Yes → Use Inventory REST API
│
├─ Need to CREATE multiple records?
│ │
│ └─ Yes → Use Transaction API
│
├─ Need to UPDATE records?
│ │
│ ├─ Keyed fields/rows, no dialogs → Use Transaction API (Status "New")
│ │
│ └─ Dialogs, disabled tabs, complex flows → Use Interactive API
│
├─ Need response dialog handling?
│ │
│ └─ Yes → Use Interactive API
│
└─ Single-record CREATE?
│
├─ High volume → Use Transaction API
│
└─ Low volume / needs validation → Use Interactive API
Hybrid Approaches
Read with OData, Write with Transaction/Interactive
The most common pattern: 1. Use OData for all reads (fast, simple) 2. Use Transaction API for creates and keyed updates (fast) 3. Use Interactive API for flows that need window logic or dialog answers
Example: Price Page Management
# Read existing pages - OData (fast)
pages = odata.get_price_pages(supplier_id=10050)
# Create new pages - Transaction API (bulk, fast)
new_pages = transaction.create_pages([...])
# Update existing page - Interactive API (reliable)
with interactive.open_window("SalesPricePage") as window:
window.change_data("calculation_value1", "0.55")
window.save()
// Read existing pages - OData (fast)
var pages = await client.OData.QueryAsync("sales_price_page",
filter: "supplier_id eq 10050");
// Create new pages - Transaction API (bulk, fast)
var result = await client.Transaction.CreateAsync(newPagesPayload);
// Update existing page - Interactive API (reliable)
await using var session = client.Interactive.CreateSession();
await session.StartAsync();
var window = await session.OpenWindowAsync("SalesPricePage");
await window.ChangeDataAsync("FORM", "calculation_value1", "0.55",
datawindowName: "form");
await window.SaveDataAsync();
await window.CloseAsync();
Performance Benchmarks
Measured against production P21 instance:
| Operation | OData | Transaction | Interactive |
|---|---|---|---|
| Read 160 records | 0.12s | N/A | ~2s |
| Create 1 record | N/A | 0.05s | 2.5s |
| Create 25 records | N/A | 1.4s | 62s |
| Update 1 record | N/A | ~0.8s* | 2.0s |
*Transaction API keyed update via Status: "New" (verified on JobContractPricing; per-line latency ~0.8s). Flows that trip prompts or disabled tabs still need the Interactive API.