Changelog
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.
All notable changes to this documentation project are listed below, grouped by date. This project uses Conventional Commits.
⚠ P21 Breaking-Change Alerts
Standing alerts for P21 platform version changes that break or silently corrupt API integrations — maintained in P21 Breaking Changes by Version. Check before any upgrade.
- 2026.1 — Interactive endpoints return an empty HTTP 500 unless
Acceptincludesapplication/json(httpx/.NET*/*defaults break), and the failed session create leaves a ghost session (409 until youDELETEit);SessionId→Id;TabNameno longer accepted on/v2/tab; three data-integrity hazards (nonexistent record loads asStatus: 2+ empty window; batched/v2/changeis non-atomic — a rejected field does not roll back its neighbours; UDT update/delete can't target rows in a 2026.1-created UDT — delete reports[0] rows deleted ... successfully!and does nothing). Found on 2026.1.5873.1 vs 2025.2.5855.0; re-verified on production 26.1.5894.1 (July 2026). → details- 25.2 —
DatawindowNamerequired in Interactive change requests (3-param form stops working). → details
2026-07-21 — v1.4.0
Cleared the open findings backlog (issues #98–#102): five live-verified 26.1 discoveries written into the docs, with four new service definitions fetched from the 26.1 play tenant (build 26.1.5894.1). All new cross-links validated against the generator's heading ids.
- feat: New recipe create-customer (Transaction
Customer) — minimal working create, plus the two non-obvious required fields:salesrep_idis hard-required and its failure surfaces as the misleading "Salesrep ID is required for a new ship to.", anddefault_branchis required but not supplied by the defaults template. End-to-end scripts in Python and C#. Closes #99 — @mrwuss - feat: New recipe create-requisition-po (Transaction
RequisitionPurchaseOrder) — requisition POs are created via a type-specific service, not by settingpo_hdr_po_type(a disabled column onPurchaseOrder). Documents the vendor-vs-supplier id distinction, the misleading line-level supplier error whose fix is on the header, the requisition-item precondition (inv_loc.requisition='Y'), and the verified result letterpo_hdr.po_type = 'R'. Added to the Common Services table and a new Purchase Order Types section with the po_type letter table. End-to-end scripts in Python and C#. Closes #101 — @mrwuss - feat: New Transaction-API section GL Dimensions in the API — GL dimensions attach at voucher/invoice/JE time, not on POs:
ConvertPOToVoucherandVoucherByItemcarry the dimension fields and theTP_TRANS_X_GL_DIMENSIONgrid, whilepo_hdr/po_linehave no dimension columns (pre-tagging a PO needs UD fields). NotesVendorInvoice500s on/definition. Closes #102 — @mrwuss - docs: Interactive Open Window now warns that opening by
Name/Titlecan return HTTP 400 "not available or user does not have permission" even where the same window opens byServiceName—ServiceNameis the only reliable identifier. Added Window-to-Service Discovery documentingframe_menu.service_nameas the window→service map (NULL = no API surface). Closes #98 — @mrwuss - docs: New OData section Undeployed / Unlicensed Windows — the postal-code-group family (
postal_code_group_hdr/_detail,salesrep_postalcode,ideal_locations_by_zip) is OData-readable but dead storage when the module is undeployed: no Transaction/Interactive surface, and Customer-create never consults it (no zip→salesrep cascade — verified by seeding then creating). Readable ≠ live. Closes #100 — @mrwuss - chore: Fetched and committed four service definitions from 26.1 (
Salesrep,RequisitionPurchaseOrder,ConvertPOToVoucher,VoucherByItem); added them to the fetch script's documented set and the definitions README. Fixedfetch_definitions.pyto merge_manifest.jsonon a partial--servicesrun instead of overwriting it (a targeted fetch previously erased the record of every other definition), and documentedP21_SCRUB_TERMSin.env.example— @mrwuss
2026-07-14 — v1.3.0
Production moved to 2026.1, so every 2026.1 claim in these docs was re-tested against a live 2026.1 tenant (build 26.1.5894.1) rather than the 5873.1 test build they were written from. Epicor's Prophet 21 Release 2026.1 Release Guide was also mined for API-relevant content — it yielded exactly one new API surface, which is now documented.
- feat: New Bulk Data API section (doc 13) — 2026.1's "Bulk Data API for User-Defined Tables" announced in the release guide, which names no endpoint (and the in-middleware SDK reference at
/docs/p21sdkdoesn't document it either). Found and fully characterized by probing a live 2026.1 tenant:POST /udtservice/api/bulkupload/{table},multipart/form-datawith the form fieldfile, comma-delimited CSV with a mandatory header row, header names matched exactly and case-sensitively, column subsets allowed, all-or-nothing per file, insert-only (no upsert; no/bulkupdateor/bulkdelete), 1,000 rows/call verified. Two hazards, both read-back confirmed: a headerless CSV returns{"isSuccessful": true}and inserts zero rows, and values are silently rounded to the column scale (1.66→1.7intodecimal(2,1); only precision overflow errors). Also:NULLis expressible only by omitting the column (blank and literalNULLboth 400, even on nullable columns), andcreated_byrecords the middleware's SQL login rather than the API user while remaining writable from the file. Documents UDT catalog discovery over OData (master_udt_definition/master_udt_definition_column) and that creating a UDT is UI-only — @mrwuss - fix: Breaking Changes § 2026.1 re-verified on production 26.1.5894.1 — entries 1–5 all still reproduce, with three corrections. The
Acceptrule is "application/jsonmust be present", not "*/*breaks" (application/json, */*returns 200;application/xmlandtext/htmlfail — full matrix added). A ghost session is cleared immediately byDELETE /sessions— the previous "wait outSessionCleanupExpiration(~6 min)" advice was unnecessary — and the ghost masks the header experiment you'd run to diagnose it. The nonexistent-record load (#5) does carry a diagnostic the original report missed:Messages: [{"Text": "Enter a valid ID or leave ID blank.", "Type": 2}]— @mrwuss - fix: Breaking Changes entry #6 rewritten — the reported mechanism ("a batch containing a non-active-tab field returns
Status: 1while silently dropping it") did not reproduce on 26.1.5894.1 across eight configurations (batched/single,DatawindowNamesupplied/omitted, tab active/inactive); the field applied every time. What does reproduce is that batched/v2/changeis non-atomic: one rejected field returns an HTTP 400 envelope with noStatuswhile the other fields in the same batch are already applied (read-back confirmed) — same partial-application outcome, different route. Entry now documents the verified behavior with the original claim recorded as a correction; the one-field-per-call mitigation is unchanged. 5873.1 is no longer available to distinguish "fixed later" from "misattributed" — @mrwuss - docs: New 2026.1 observations in doc 14 — the middleware version has no endpoint but rides the session-create response (
Properties[0].Properties.fullversion), the only reliable way to confirm your build;GET /v2/datareturns only a varying subset of a window's datawindows (a datawindow's absence proves nothing — it is not a reliable field-level read-back); a nonexistentDatawindowNamefails loudly with HTTP 400 rather than silently; andDatawindowNameis optional for header fields on 26.1 though still required on 25.2, so keep sending it — @mrwuss - docs: 2026.1 release-guide notes — Epicor attributes the release's platform work to ".NET Platform Modernization: migration to .NET 10 and a re-architected middleware home page", consistent with (though not confirmed by Epicor as the cause of) the content-negotiation and contract changes above; 2026.1 is the last release to support SQL Server 2016 (2026.2 requires 2019+); Crystal Reports reaches SAP end-of-support 31 Dec 2028, with Report Studio and Epicor Forms Service as the forward path — @mrwuss
- docs: The UDT
row_uidhazard promoted into the Breaking Changes registry as 2026.1 entry 7, the standing alerts header, the Error Handling guide (a new UDT Service Errors section keyed on the literal strings you'd search for —"Invalid Row Uid!","[0] rows deleted ... successfully!","Conditions cannot be blank or none!","Invalid UDT table"/"Incompatible table for bulk insert"), and the task index. Doc 14's overview was softened to match what we can actually prove: this entry has no prior-version comparison (no pre-2026.1 tenant remained), so rather than let the page keep implying every entry was A/B'd, the registry now states that entries lacking a prior-version check say so — and entry 7 says so — @mrwuss - feat(site): Per-section "Copy BBCode" buttons on the Changelog and Breaking Changes — for quoting a release, a version's findings, or a single entry on the P21 forum. Hovering any heading (h2/h3/h4) reveals the button; it copies that section only, stopping cleanly at the next heading of equal or higher rank, so an entry copies alone while its parent version copies with every entry nested under it. Every link is rewritten to an absolute URL so it still resolves once pasted — including same-page anchors, and resolved against the page's canonical published URL rather than
document.baseURI(which would bake in afile://path when the page is opened locally). Output is BBCode targeted at a forum that accepts no[list]/[*],[quote]or[code]: lists become•bullet lines (nested ones indented), tables one line per row (cells joined with|, header bolded), code blocks and inline code plain text with line breaks preserved, and blockquotes inlined — the ⚠ callouts already lead with bold, so they still read as warnings. Verified across all 45 sections on both pages: no forbidden tags, no relative URLs, no unbalanced tags — @mrwuss - fix(site): 23 broken anchor links repaired across the docs and recipes — every heading containing an em-dash (e.g. "Upsert Semantics — Keyed Rows…", "No Joins — Chain Queries by UID", "Cost Model — Know This…") was linked with a double hyphen while the generator emits a single one, so the links silently landed at the top of the page instead of the section. Eight of them were in
INDEX.mditself — the routing layer whose entire job is jumping to the right section — plus five recipe pages and four cross-doc references. Found by validating every link against the generated HTML ids rather than a re-implemented slug rule; all 277 anchor links acrossdocs/**/*.mdnow resolve — @mrwuss - docs: Dropping a UDT also needs an OData schema refresh — until it runs, queries against the dropped table return
404 "Invalid object name 'dbo.{udt}'.", distinct from the empty-bodied 404 of a table that was never exposed; the wording tells you which situation you're in — @mrwuss - fix: UDT update and delete cannot reach data in a 2026.1-created UDT (doc 13) — both endpoints are hard-wired to a literal
row_uidcolumn, but 2026.1's User Defined Table Maintenance names the primary keyudt_{tablename}_uidand creates norow_uid. Update then returns400 {"error":["Invalid Row Uid!"]}for every condition (the real PK name, any other column, any casing, string or int), and delete returns HTTP 200{"errorNo": 0, "errorMessage": "[0] rows deleted ... successfully!"}while deleting nothing — only the[0]count betrays the no-op. Also found: delete readsconditionsfrom the payload's top level, not nested inrows[]as documented (the nested form 400s with "Conditions cannot be blank or none!"); both forms are now shown. Found while clearing the bulk-API probe rows — the deletion silently "succeeded" four times before a read-back showed the count unchanged. Guidance added: verifyrow_uidexists ($select=row_uid) before relying on these endpoints, and check the row count inerrorMessagerather thanerrorNo— @mrwuss - fix: Propagated the corrected 2026.1 behavior to every page that still taught the superseded version — Error Handling (the
Acceptrule,DELETE-to-clear-the-ghost, the ghost masking the header experiment you'd run to diagnose it, plus a new entry for the partially-applied batch after a 400), Authentication, and Interactive API — which now shows both session-create response shapes (25.2SessionId+Statusvs 2026.1Id+Properties) instead of only the 25.2 one — @mrwuss - docs: Interactive API — documented that P21 exposes no version endpoint and the middleware build rides the session-create response (
Properties[0].Properties.fullversion), the only reliable way to confirm which build you're on before trusting version-specific behavior — @mrwuss - docs: OData —
$metadatais on the collection path (/odataservice/odata/table/$metadata; the service-root form 404s), returns JSON CSDL rather than XML EDMX (~4 MB, ~3,400 tables), with a snippet for listing exposed tables fromns.container— the fastest way to settle "is this table actually exposed?" before debugging a 404. Also: an unknown$selectcolumn returns 404, not 400, which is easily misread as a missing table or permission (a wrong column name is the likelier cause) — @mrwuss - docs: Entity API terminology corroborated by Epicor's in-middleware SDK reference (
/docs/p21sdk), whose first-party catalog lists exactly four APIs — Transaction ("previously known as the v2 API", in Epicor's own words), Entity, Interactive, Data Services — split by capability rather than URL prefix. The UDT Service and its new Bulk Data API appear in none of them, so the SDK catalog is not exhaustive either — @mrwuss
2026-07-10 — v1.2.0
- feat: New P21 Breaking Changes by Version registry (doc 14) — a first-class page cataloging middleware changes that break or silently corrupt integrations, checked before upgrades. Launches with 2026.1 (verified 2026.1.5873.1 vs 2025.2.5855.0 during upgrade validation; reported to Epicor): Interactive endpoints return an empty HTTP 500 without
Accept: application/json(httpx/.NETAccept: */*defaults fail) with a ghost-session side effect (409 "Session already exists" until cleanup → alternating 500/409); session-create response renamedSessionId→Id;/v2/tabbindsPageNameonly; and two silent-false-success hazards — nonexistent record loads returnStatus: 2with an empty window (wasStatus: 0), and multi-field/v2/changesilently drops non-active-tab fields while returningStatus: 1— each with its verified mitigation (existence pre-read; one-field-per-change-per-active-tab + read-back). The 25.2DatawindowNamechange is consolidated into the same registry. Standing Breaking-Change Alerts header added to the top of this changelog; cross-references added in 00/04/06/INDEX/CLAUDE.md — Fixes #96 — @mrwuss
2026-07-10 — v1.1.1
End-to-end alignment audit: nine area agents checked every tracked file against the session's live-verified findings; ~118 findings triaged and fixed in one pass (PR #94). Highlights:
- fix: Interactive API client code in the batch-patterns guide taught calls that fail —
?windowId=on/v2/window//v2/data(only/v2/toolstakes it), eventDataaccessed as a dict (it is a key/value list),resp["Tools"]on a bare-array response — corrected in both languages; five legacy Python interactive scripts carried the same parameter bug — Fixes #93 — @mrwuss - fix: SalesPricePage code tables corrected against live
code_p21reads (220=Source, 221=Price, 227=Value; earlier published values were misassigned), and that doc's C# tabs rewritten from a nonexistent endpoint surface to the real v2 API — Fixes #93 — @mrwuss - fix: Response-window examples in both languages modernized from the pre-2026 "dialogs cannot be answered" framing to the verified
GET/POST /v2/toolsanswer flow (live-tested; onlyw_messageboxes remain auto-answered), which also surfaced and fixed a latent VALUES-datawindow name bug — Fixes #93 — @mrwuss - fix: Both languages' "update existing" examples built payloads that would INSERT (no record identification) — corrected with the key field included and an explicit unverified-for-this-service caveat; all
/api/dataaccess/v1OData URLs replaced (live-verified 404;/odataservice/odatais the working route) — Fixes #93 — @mrwuss - feat: Every write example now dry-runs by default (
--execute/ typingEXECUTE) and verifies with a read-back where practical; shared clients harden the router call (redirects + XML fallback) and requireDatawindowName;Valueis a string in every payload-building path — Fixes #93 — @mrwuss - docs: Selection guide now covers the Inventory REST and UDT Service APIs and routes
/api/sales/orderscorrectly; error guide gains theStatus: "Existing"and report-service traps; changelog contributors table refreshed — Fixes #93 — @mrwuss
2026-07-10 — v1.1.0
Example-layout reorg: the repo now serves all four consumption styles symmetrically — Python, C#, JSON, XML.
- refactor: Python examples moved from
scripts/toexamples/python/(clean git renames) for symmetry withexamples/csharp/;scripts/now holds repo tooling only (generate_html.py,fetch_definitions.py,validate_payload.py). All path references updated across docs, recipe pages, README, CLAUDE.md, C# header comments, and.gitignore; newexamples/python/README.mdmirrors the C# one — Fixes #87 — @mrwuss - feat: New
examples/payloads/library — 11 JSON + 9 XML standalone, copy-ready request bodies for the documented tasks, generated from one source of truth (the XML can never drift out of DataContract element order) and every file machine-verified withscripts/validate_payload.py. Report (pdfreport) payloads ship JSON-only pending XML verification of that endpoint. Recipe pages link their payload files alongside the end-to-end code files — Fixes #87 — @mrwuss - feat:
validate_payload.pynow recognizesPOST /api/v2/transaction/getrequest bodies (ServiceName/TransactionStatesshape with object-styleKeys) — @mrwuss
2026-07-10 — v1.0.0
First tagged release. This wave cross-checked the docs against a community process playbook — every disputed claim was live-verified against a 25.2 test tenant — and restructured the repo for progressive disclosure: task routing, a schema library, a recipes cookbook, end-to-end example files, and payload-correctness tooling. Verified findings credit: Alex Westemeier.
Corrections (our docs were wrong):
- fix: Report-service discovery —
GET /api/v2/services?type=reportreturns an empty list, not the report services; them_*services are hidden from/api/v2/servicesentirely (exactly 299 transaction objects) but remain fully callable viadefinition/defaults/pdfreport. Documented the definition-probe andwindow_x_menumenu-leaf discovery paths. Also:GET /v2/tools?id=returns HTTP 400 (not 500), and the router endpoint without a trailing slash can respond 307 (breaks non-redirect-following clients) — all verified live — Fixes #54 — Alex Westemeier, @mrwuss - fix: API Selection Guide no longer steers all updates to the Interactive API —
Status: "New"+ keyed rows is a verified Transaction API update path and an upsert (inserts when the key doesn't match; 81 lines added in one verified run). Related JobContractPricing corrections: commission costs ARE writable withIgnoreDisabled: true(previously documented as Interactive-only);end_date >= todayheader validation; one-transaction-per-POST rule for line inserts (header optimistic-concurrency collisions + duplicateline_no);pricing_methodmust precedeprice(silent $0 line). NewIgnoreDisabledsection: unlocks disabled columns AND disabled sub-tabs (contract BINS), top-level placement only (silently ignored inside a Transaction) — Fixes #56 — Alex Westemeier, @mrwuss - fix: Reconciled the April 2026 "form-type response windows are dismiss-only" limitation with the July 2026
TabName: nulleditable-response-window finding — the earlier tests addressed popup fields withTabName: "FORM"; retry withTabName: nullbefore concluding a popup is dismiss-only — Fixes #68 — @mrwuss - fix(site):
docs/INDEX.mdrendered toINDEX.html, which on case-insensitive filesystems is the same file as the landing pageindex.html— the Task Index HTML was silently clobbered every build and its sidebar links 404'd on the published site. The generator now emits it astask-index.html— Fixes #84 — @mrwuss - Not adopted after testing: the community claim that
company_idis a disabled column on the JobContractPricing FORM did not reproduce (header saves pass with it included) — the documented #44 update path stands.
New verified content:
- docs: PDF report generation expansion — the wrong-endpoint trap (
/api/v2/transactionacceptsm_*payloads, returnsSucceeded, emits nothing), per-serviceUseCodeValuesdifferences (m_pickticketsrequirestrue+ code values;falsereturns HTTP 500), a workedm_pickticketsexample (creates the pick-ticket record at the requested location AND returns the PDF;printed='Y'prerequisite), and print flags on/transactionreturning PDFs atResults.Transactions[].Documents[]with the make-location limitation — Fixes #58 — Alex Westemeier, @mrwuss - docs: Order service —
source_loc_ideffectively required (tax-jurisdiction error),requested_datemust followorder_date, DynaChange prompts auto-answered with the default silently kill lines; new Interactive API "Sales Order Entry with Assembly Lines" flow (assembly promptcb_1, date-cascadew_response_commonon new orders,takerdefaults to the API user, quickmode bypasses the assembly prompt) — Fixes #60 — Alex Westemeier, @mrwuss - docs: Item service nested-element recipes — primary bin (Form→List→Form) and primary supplier (Form→List→List) with the write-flag vs read-field distinction and the silent no-op when the supplier lacks a location-level row; "Item Issues Detected" rule-callback answering (
cb_1, retrieve-time popups); the detail-formselect_rowtrap — Fixes #62 — Alex Westemeier, @mrwuss - docs: BinLocation bulk bin creation — three-field keyed create, mandatory top-level
IgnoreDisabled, codes-not-uids,ON/OFF↔Y/Nflag conversion, clone-a-twin practice,p21_view_binread-back — Fixes #64 — Alex Westemeier, @mrwuss - docs: Production Order Lifecycle (end-to-end) — stock netting on sales-order auto-create, make-location pick-ticket limitation, labor-before-print timing, the shell-confirm trap (a bare Transaction API confirm flips status/
qty_confirmedbutqty_applied=0and moves no stock — confirm interactively), completion mechanics (separatebin_cd/unit_quantitycalls, per-componentnew_costoverride, status codes 702/1962/1268), Quick Time Entry strict field order and open-period requirement,Shipping(ship + invoice in one save),InventoryAdjustment, and the cost model (receipt vs moving-average COGS, pooling) — Fixes #66 — Alex Westemeier, @mrwuss - docs: OData — explicit no-
@odata.nextLinknote, "No Joins — Chain Queries by UID" pattern withp21_view_*guidance, base-host-not-ui_server note; Interactive — key fields commit the cursor (later fields in the same change call silently ignored), integer-string numerics, the verified BINS unlock recipe for existing contracts (load byjob_no) — Fixes #68 — Alex Westemeier, @mrwuss - docs:
UseCodeValues↔code_p21mapping (labels fromcode_p21language_id 9; DB/OData return the integercode_no) with verified enum maps; definition-endpoint HTTP 500 "Window <> is not available" documented as environment availability, not permissions (238/299 fetchable on the test tenant) — Fixes #70 — Alex Westemeier, @mrwuss - docs: Payload Anatomy — type-annotated TransactionSet skeleton and a mistakes→symptoms table (
Keysas string, misplacedIgnoreDisabled, quoted booleans, object-for-array nesting, non-stringValues, wrong property case, cascade-breaking field order); XML Payloads — full content negotiation verified on every/api/v2endpoint (all four Content-Type/Accept combinations), the mandatory DataContract namespace, alphabetical element order (top-level violation → HTTP 500; nested → silently dropped element → NullReference failure),Keysarrays namespace,TransactionStateRequestroot for/transaction/get, and the fetch-the-Template-as-XML practice — Fixes #82 — @mrwuss
Structure & tooling:
- feat: Task Index routing layer —
docs/INDEX.mdmaps ~80 tasks to exact section anchors so readers (and AI agents) load only what a task needs; navigation convention documented — Fixes #72 — @mrwuss - feat: Service-definition schema library —
definitions/holds sanitized full-field definition JSON (every DataElement, field, key, type, label + payload template) for all 21 documented services;scripts/fetch_definitions.pyrefreshes and sanitizes (drops environment-specificufc_*fields, redacts lookup-backedValidValuesthat carry live instance data, scrub-term gate) — Fixes #74 — @mrwuss - feat: Recipes cookbook —
docs/recipes/with 10 self-contained task pages (complete payload, full runnable Python + C# example, verified gotchas, verify read-back) plus a conventions README; INDEX routes tasks to recipes first — Fixes #76 — @mrwuss - feat: End-to-end example files —
examples/python/recipes/(dry-run by default,--executegates writes) and theexamples/csharp/Recipes/solution project (menu runner,EXECUTE-gated writes); every recipe page links its files — Fixes #79 — @mrwuss - feat(site): Recipes published on the HTML site — subfolder conversion with depth-aware sidebars, a Recipes nav section and landing-page grid, and repo-file links (
definitions/, example files) rewritten to GitHub; landing page gains the Task Index card and the previously missing Production & Labor and UDT cards — Fixes #77 — @mrwuss - feat: Offline payload validator —
scripts/validate_payload.pychecks JSON and XML payload files against the shape rules anddefinitions/schemas (exact paths to each problem, did-you-mean suggestions, XML namespace + element-order enforcement, verified field-order rules);--self-testincluded — Fixes #82 — @mrwuss
2026-07-06
- docs: Correct Entity API taxonomy — Epicor's "Entity API" is an umbrella term for two APIs: the REST API (the
/api/entity/,/api/inventory/, and/api/sales/endpoint families) and the eCommerce API (Entity SOAP API); URL segments are arbitrary and don't define API boundaries, so the repo's/api/entity/-only framing and "Inventory is a separate API" language were wrong. Removed the incorrect warning that category URLs "do not work":/api/sales/ordersexists and responds (verified July 2026: ping 200,/newreturns a full order template, GET by order number returns ~70 fields,/approveroute present); other category families (sales/customers,purchasing/*,ar/*, …) 404 on the tested tenant. Adds a Terminology section and an "Other REST Endpoint Families" section — Felipe Maurer (P21WWUG, taxonomy correction, 25.1 middleware evidence, forum topic), verified and documented by @mrwuss — Fixes #53 - docs: Add PurchaseOrder notepad writes documentation (Interactive API) — header notes (
po_hdr_notepad, PO Notes tabTABPAGE_7, datawindowtp_7_dw_7, toolscb_add/cb_edit) vs line notes (po_line_notes,TABPAGE_21/tp_21_dw_21after selecting a row intp_17_dw_17, toolscb_add_line/cb_edit_line), full popup walkthrough (w_notepad_response_lite:_dw_hdr/_dw_areas/_dw_select,TabName: null,cb_select_all→cb_ok), and the silent-misfile warning: both tools are labelled "Add Note" butcb_add_linefiles the note against the currently-selected line with HTTP 200/savesucceededand no error. RequiresResponseWindowHandlingEnabled: true— withfalsethe add tool returns HTTP 400 "Unexpected response window". Both recipes and the misfile scenario verified end-to-end on a live test tenant (July 2026, read-back confirmed) — Fixes #49 — @mrwuss - docs: Document
GET /api/v2/definition/{Service}as the authoritative schema map — response shapeTransactionDefinition.DataElementDefinitions[]withName,DatawindowName,Type,KeyFields, andFieldDefinitions[](Name,DbColumnName,DataType,Required); added as window discovery technique #7 in the Interactive API guide. Warning boxes in both guides:TABPAGE_Nnames are not sequential with the visible tab order (PurchaseOrder carries 37 tab pages, many hidden — the grid that looks like the second tab isTABPAGE_17), so match on the datawindow name (tp_N_dw_N/d_...) or read the window'sTabPageList; live testing on two servers showed the Interactive window'sTABPAGE_Nnames matching the Transaction definition 1:1 — Fixes #50 — @mrwuss - docs: Add "Verifying Writes" section to Interactive API guide — a save can return
Status: 1withsavesucceededfor the primary datawindow while a child-grid change never persists (verified: a correctly-persisted note and a silently-misfiled one produce identical save responses); status semantics vary across P21 versions, and the save response never includes inserted child keys (note_idappears in the parent grid once the notepad popup commits, but only a read-back proves persistence). Recommendation: read the record back viaPOST /api/v2/transaction/get(or OData) before treating the write as done — verified live: the read-back recovered the server-generatednote_idand located a misfiled note; cross-linked from the Transaction API guide — Fixes #51 — @mrwuss
2026-06-15
- docs: Document Inventory REST API
ItemDesccharacter-set and whitespace behavior — on a 26.1 tenant the API enforces no symbol restriction (all printable ASCII" ' \& < > # / \ | , ; : . ( ) [ ] { } * + = % $ @ ! ? ~ ^ _ -and Unicode such asé,½,°round-trip intact via GET → PUT → GET); only the 40-char limit (41+ **silently discarded** on PUT, HTTP 200) and trailing-whitespace trim apply. **Version/pipeline caveat:** 25.x tenants and downstream consumers (reporting, label printing, EDI) may reject characters the 26.1 REST API accepts — the double-quote"` is a known offender — so strip risky symbols from descriptions and part numbers before writing if targeting 25.x or feeding reporting. Includes a round-trip probe snippet. Verified against Prophet21Play (26.1) — Fixes #47 — @mrwuss
2026-05-22
- fix: Correct JobContractPricing update guidance — Transaction API does update existing
job_price_linerows when called withStatus: "New"and the FORM key fields (company_id,contract_no,job_no,end_date) inEdits(notKeys); previous docs incorrectly deflected readers to the Interactive API. Adds Updating an Existing Contract subsection with verified payload shape, notes thatpricing_methodSource → Price conversion works in the same call, and inlines a/api/v2/transaction/getretrieval example. Empirically verified by 173 successful price updates against contractA120-12on a production tenant (HTTP 200, OData re-read confirmed). TheStatus: "Existing"500 caveat remains as an "unused — use New instead" note, no longer a write ban — Fixes #44 — @mrwuss via PR #45
2026-04-16
- docs: Add UDT Service API documentation (
docs/13-UDT-Service-API.md) — complete CRUD documentation for/udtservice/api/udtdata/endpoints (insert, update, delete), OData read patterns, response format quirks, SQL keyword false positives, SaaS hostname differences, Python and C# examples — Felipe Maurer (discovery and testing), David Sokoloski (P21 help docs reference), Brad Vandenbogaerde (database tables, SaaS hostname fix), John Kennedy (SQL keyword issue), Jon Christie (response format quirk), @mrwuss - docs: Add Inventory REST API pricing endpoints — two verified V2 pricing URL patterns, URL encoding requirements for special characters, forward slash (
/) encoding confirmed broken (returns 404), verified pricing response structure with availability data — Felipe Maurer, John Kennedy, @mrwuss - docs: Add Interactive API response window handling for tabless windows —
TabName: nullpattern for changing fields on popup dialogs, common response window buttons — Jon Christie — @mrwuss - docs: Add window discovery techniques section — GetState, GetTools, GetData, result event inspection, P21 SQL Information dialog, browser DevTools — @mrwuss
- docs: Add V1 REST endpoint reference table — internal SDK endpoints for debugging and network trace analysis — @mrwuss
- docs: Expand 25.2 DatawindowName breaking change — add ConvertPOToVoucher (Jeff Patterson, Josiah Shollenberger), Order Entry (Neil Timmerman), Clippership Auto Shipping (Josh Owen), Doc Links (Jaime Nelson) to affected windows list; bug confirmed through 25.2.5776.1, acknowledged by Epicor as development bug; add PO Receiving Group fix example — David Sokoloski (first discovered 4-param workaround), Jeff Patterson (confirmed fix) — @mrwuss
- docs: Add PDF Report Generation section to Transaction API —
/api/v2/process/pdfreportendpoint for generating base64-encoded PDF documents (purchase orders, pick tickets), verified servicesm_reprintpurchaseordersandm_reprintpicktickets, Python and C# examples — Jeff Poss (endpoint discovery), @mrwuss - docs: Add Stored Procedure Executor section to Transaction API —
m_storedprocedureexecutorservice for loading SP definitions via Transaction API, UID lookup workflow,argument_listparameter discovery, database tables (stored_procedure_def,spe_parameter_info,spe_procedure_info) — Felipe Maurer, Kevin Landry, Brad Vandenbogaerde, @mrwuss - docs: Add DynaChange and Popup Handling section to Transaction API — DynaChange enforcement in TAPI workflows, popup suppression pattern for API user profiles, Visual Rule limitations with response/callback attributes, "Column is disabled" root causes, HTTP 200 response validation gotcha — Felipe Maurer, Brad Vandenbogaerde, Justin Cassidy, Neil Timmerman, @mrwuss
2026-04-10
- docs: Correct Inventory REST API — existing
inv_locfields CAN be updated via GET → modify → PUT (previously documented as append-only), add ItemDesc 40-char limit, POST 307 redirect, location soft-delete viaDelete: "Y", PurchaseDiscountGroup/SalesDiscountGroup fields, minimum create payload — Fixes #31 — @mrwuss - docs: Add JobContractPricing service documentation — full-service structure (25 DataElements), multi-line break interleaving pattern, 15-tier break structure, non-break vs break line patterns, Status "Existing" NullReferenceException (platform-wide bug), commission cost column limitations — Fixes #32 — @mrwuss
- docs: Add Assembly service documentation — full-service structure (15 DataElements), component_type valid values (hose-specific), copy_item_id field, item-must-exist-first validation, Part + Assembly creation workflow, Status "Existing" bug — Fixes #33 — @mrwuss
- docs: Add Interactive API operational patterns — tab unlock sequences (JobContractPricing example), add_row Status=2 creates row despite failure status, response window type taxonomy (button-only vs form+button vs message box), UOM auto-population best practice, timeout recommendations — Fixes #34 — @mrwuss
- docs: Expand authentication documentation — token TTL and reuse patterns, multi-API token reuse, TokenManager class examples (Python + C#)
2026-04-04
- docs: Expand consumer key authentication documentation — verified consumer key + username works for Interactive API sessions, added SOA Admin configuration fields, JWT token claims, API-specific behavior table, scope behavior, Python and C# code examples — @mrwuss
- feat: Add
consumer_usernameto P21Config andload_config()for consumer key auth support - chore: Scrub all personal and company-specific data from repository history via git-filter-repo
2026-03-06
- feat: Add Production & Labor API documentation — TimeEntry service for recording labor hours against production orders, ProductionOrder service with full field definitions (54 header fields, assembly lines, components, labor entries, completions, routing), Labor/LaborProcess services for labor code maintenance, 24 production-related Transaction API services discovered, 13 Interactive API windows verified working — @mrwuss
- feat: Add Python example scripts for production/labor — service discovery, TimeEntry definition, labor hour recording, ProductionOrder definition (
examples/python/production/) - feat: Add C# example code for production/labor — mirrors Python examples (
examples/csharp/Production/) - docs: Update API Selection Guide with production/labor use cases and decision table entries
- docs: Update Transaction API with production & labor services table
- docs: Update Interactive API with production & labor windows table
2026-02-25
-
feat: Add C# code examples alongside Python across all documentation — tabbed code blocks (Python/C#) in generated HTML with global language sync and localStorage persistence, 23 C# console app examples mirroring every Python script, shared C# client library (
examples/csharp/Common/) with auth, config, and HttpClient wrapper — @mrwuss -
fix: Correct Interactive API
ResultStatusenum mapping —None=0, Success=1, Failure=2, Blocked=3(was incorrectly0=Failure, 2=Blocked, 3=Dialog), verified against source (ResultWrapper.cs) and live API — PR #26 - docs: Update Data Structures Reference to show integer status types with numeric values
- docs: Document
/toolsendpoint workaround for non-message-box response windows —GET /toolsdiscovers buttons,POST /toolsclicks them (verified onw_inventory_scan_lookup) - docs: Update Event Data format documentation — confirmed KV-list format
[{"Key": "...", "Value": "..."}] - fix: Document P21 25.2 breaking change —
DatawindowNamenow required in Interactive API change requests (3-parameterChangeDatamethod no longer works, must use 4-parameter form). Affects Item, PO Receiving Group, Delivery List, Group Pick Ticket, and likely other windows. Updated all code examples and batch processing patterns. Source: community forum reports. - fix: Rewrite
06_complex_workflow.pyfrom v1 to v2 — endpoints, payload format (ListnotChangeRequests),DatawindowNamecasing, integer status checks, v2 save format - fix: Fix
get_opened_window_id()in reusable client to handle KV-list Event Data format[{"Key": "windowid", "Value": "..."}] - fix: Fix Error Handling doc — Blocked status is integer
3(not string), Event Data uses KV-list format
2026-02-17
- docs: Add standalone Inventory REST API documentation — moved from Entity API doc, added verified PUT/POST behavior, multi-company inventory workflow (GET → Append → PUT), error examples, automation patterns — Sibin Francis (@sibinfrancisaj) via PR #25, verified and restructured by @mrwuss
- fix: Update inv_loc write access known issue — PUT can append new inv_loc records via Inventory REST API (partially resolved)
2026-02-16
- feat: Add Postman Collection for all P21 APIs — pre-configured requests for Auth, OData, Transaction, Interactive, and Entity APIs with auto-capture test scripts — NextTWis (@NextTWis) via PR #24
- fix: Correct API endpoints in Postman collection — verified all URLs against live server, fixed Entity/Interactive/Transaction paths and payloads — @mrwuss
2026-02-13
- docs: Add Inventory REST API documentation to Entity API guide — verified endpoints, caveats, extended properties — Sibin Francis (@sibinfrancisaj) via PR #23
- fix: Disable Jekyll rendering and add root URL redirect to HTML docs — @mrwuss
2026-02-12
- feat: Add reusable P21 API client (
examples/python/common/client.py) with sync/async support, namespace helpers for all 4 APIs, and auto token refresh — Claude Jones (@RadAJones) via PR #16 - fix: Address CodeRabbit + live API testing feedback on client — duplicate parser removal, query param fixes (
?id=vs?windowId=), entity address guards, response window forwarding — @mrwuss - docs: Add XML token responses section, query parameter testing results, and cross-reference updates across Auth, Interactive, Entity, and Error Handling docs — @mrwuss
- docs: Fix Entity API address limitations, add SOAP/mobile endpoints and error codes — @mrwuss
- feat: Add sidebar navigation with page index and on-page table of contents to all HTML docs — @mrwuss
- docs: Expand Transaction API with commands endpoint, async limits, and special scenarios — @mrwuss
- docs: Expand Interactive API with session params, data structures, and missing endpoints — @mrwuss
- docs: Fix OData pagination guidance — page size defaults and performance — @mrwuss
2026-02-11
- docs: Add complete field listings for all Entity API templates — @mrwuss
- docs: Rewrite Entity API docs based on verified live testing — confirmed working, composite keys, address limitations — @mrwuss
- docs: Add OData Dataservice Permissions prerequisites and fix OData version (v3, not v4) — @mrwuss
2026-02-09
- docs: Add production learnings from 700+ bulk Interactive API operations — session batching, error recovery, page expiration patterns — @mrwuss via PR #11
2026-01-20
- docs: Add Interactive API v1 vs v2 differences — endpoint comparison, migration guide — @mrwuss via PR #9
- chore: Cleanup repo — reorganize HTML to
docs/html/, fix broken paths, restore Known Issues — @mrwuss
2026-01-19
- docs: Add working endpoint for responding to P21 dialogs — @mrwuss
2026-01-02
- docs: Add row selection bug workaround and
inv_locexample for Interactive API — @mrwuss - docs: Add Interactive API v1 vs v2 differences — @mrwuss
2025-12-27
- docs: Add lessons learned from Cube Writer project — session pool contamination patterns — @mrwuss via PR #8
2025-12-26
- docs: Add disclaimer to all documentation pages — @mrwuss
- docs: Add SalesPricePage dropdown codes reference — @mrwuss via PR #1
2025-12-25 — Initial Release
- feat: Initial project setup with full documentation for all 4 P21 APIs — @mrwuss
- docs: Authentication — token endpoints (V1/V2), credentials vs consumer keys, API scopes, token refresh
- docs: API Selection Guide — decision flowchart and comparison table
- docs: OData API — query syntax, filtering, pagination, example scripts
- docs: Transaction API — service discovery, bulk operations, async patterns, example scripts
- docs: Interactive API — session management, window operations, response handling, example scripts
- docs: Entity API — CRUD operations on customers, vendors, contacts, addresses
- docs: Error Handling — HTTP status codes, API-specific errors, Python patterns
- feat: GitHub Pages support with card-based landing page
- feat: HTML generation script with print/PDF support
- feat: Community contribution templates (CONTRIBUTING.md, issue templates)
Contributors
| Contributor | GitHub | Contributions |
|---|---|---|
| @mrwuss | @mrwuss | Project creator, all documentation, HTML generation, maintenance |
| Claude Jones | @RadAJones | Reusable P21 API client with sync/async support (PR #16) |
| Sibin Francis | @sibinfrancisaj | Inventory REST API documentation (PR #23) |
| NextTWis | @NextTWis | Postman Collection for P21 API verification (PR #24) |
| Alex Westemeier | @AWestemeier | Report-service discovery, Transaction upsert + IgnoreDisabled findings, production lifecycle verification |
| Jeff Poss | PDF Report Generation endpoint discovery | |
| Felipe Maurer | Entity API taxonomy correction, UDT Service API discovery and testing, Inventory pricing endpoints, Stored Procedure Executor UID discovery, DynaChange enforcement in TAPI | |
| Kevin Landry | Stored Procedure Executor execution via Interactive API | |
| Brad Vandenbogaerde | UDT database tables and SaaS hostname fix, SP Executor database tables, Visual Rule response/callback TAPI limitation | |
| Justin Cassidy | DynaChange as root cause for "Column is disabled" errors | |
| Neil Timmerman | TAPI HTTP 200 response validation gotcha | |
| John Kennedy | UDT Service SQL keyword issue, Inventory pricing URL-encoding testing (forward-slash limitation) | |
| Jon Christie | UDT response format quirk, TabName: null pattern for tabless response windows |
|
| David Sokoloski | UDT P21 help docs reference, first discovered the 25.2 DatawindowName 4-parameter workaround | |
| Jeff Patterson | Confirmed the 25.2 DatawindowName fix, PO Receiving Group and ConvertPOToVoucher reports |