On this page
Task Index — Find the Right Section Fast
How to use this repo without drowning in it: the numbered docs are the deep manual — most are over 1,000 lines. Don't read a whole doc for one task. Find your task below and open only the linked section. If you're an AI assistant: read
CLAUDE.md, then this index, then just the sections your task needs.
Every task assumes you already have a token and (for Transaction/Interactive) the UI server URL — see Getting a token below.
Doing, not studying? The recipes cookbook has self-contained copy-and-run pages (complete payload + full script + gotchas) for the most common tasks — start there and fall back to the manual sections for depth.
First call of any session
| Task | Where |
|---|---|
| Get a bearer token (v2, credentials in body) | 00 § Method 1: User Credentials |
| Authenticate with a consumer key | 00 § Method 2: Consumer Key |
| Configure a consumer key's API Scope (URL scopes, OData table allow-list) | 00 § API Scopes |
| Get the UI server URL (Transaction/Interactive base) — 307 redirect gotcha | 00 § UI Server URL |
| Token TTL / reuse across APIs | 00 § Token Lifetime and Reuse |
| Detect P21 server version / production flag (undocumented endpoint) | 00 § Server Info Endpoint |
| Fix "not authorized" (P21 user permissions) | 00 § P21 Permissions — or, if using a consumer key, its scope may simply exclude the table you asked for; decode the token's aud claim before assuming it's a permission gap |
| Pick which API to use for a task | 01 API Selection Guide (short — read whole) |
| A write failed — will the other API get through? (usually no) | 01 § Interactive Is Not an Escape Hatch |
Read data (OData)
| Task | Where |
|---|---|
| Query a table or view | 02 § Query Parameters |
| Filter syntax, operators, string functions | 02 § Filter Expressions |
Only active rows — row_status_flag or delete_flag, depending on the table |
02 § Active Record Filter — soft-deleted rows keep their old values and dominate some tables |
Compare two columns in a $filter (total_amount gt amount_paid) |
02 § The right-hand side is always a literal — 404 on typed columns, silently zero rows on strings |
Long or chain 404s with an HTML page, or you need hundreds of ids in one filter |
02 § Long filters and POST /$query — 8,192-character query string, 799-term flat chain; POST {collection}/$query with a text/plain body |
in (...) or $apply (aggregate, groupby) returns rows but ignores the clause |
02 § What the service supports — both are accepted and silently ignored |
| Money or quantity sums are off by a fraction | 02 § Decimals arrive as JSON numbers — parse as decimal, or send IEEE754Compatible=true |
Order lines for open orders only (lines carry no completed / approved / projected_order) |
02 § Order lines carry no header status |
| Age open AR / build a receivables aging | recipes/ar-aging — runnable, pages invoice_hdr, buckets on each invoice's own net_due_date |
| Age open AR / "how late is this invoice?" | 02 § customer.terms_id is the default for the next document — age on the invoice's own net_due_date |
Filter 404s on a value that looks right (Edm.Decimal vs Edm.String) |
02 § Active Record Filter — many *_id columns are numeric; don't quote them |
Company column is company_id on some tables, company_no on others |
02 § Company Scoping |
Read a customer/supplier/ship-to mailing or physical address over OData (address table) |
02 § Common Tables — columns keep the mail_/phys_ prefix (mail_address1, not address1); 404 without it |
Read P21 user accounts (users) — 401, or 200 with an empty value |
02 § One table is withheld from every token: users — withheld from consumer keys and password grants by design; no OData workaround |
| Does an OData read immediately after a Transaction write ever return stale data? | 02 § Read-after-write consistency — 125/125 clean on the table: surface, light load; view: untested |
Traverse relationships (no joins — chain by _uid) |
02 § No Joins |
| Page through large result sets (no nextLink) | 02 § Pagination Helper · 02 § Page Size Guidance |
Date filters (now() is unsupported) |
02 § now() Not Supported |
| New table/column missing from OData | 02 § OData Schema Refresh |
| Empty-bodied 404 on a table you know exists | 02 § The table/view split — views live on /view/, base tables on /table/; asking the wrong one 404s exactly like a missing object |
Read a view (p21_view_*), or a user-defined table (*_ud, site-custom) |
02 § The table/view split — views on /view/; *_ud are ordinary base tables on /table/ |
| Table reads fine but is empty/inert (undeployed feature, e.g. zip→rep) | 02 § Undeployed / Unlicensed Windows |
Denormalized, search-friendly views (p21_view_es_*: customer, item, order, invoice, etc.) |
02 § Enterprise/Global Search Views |
Value inventory adjustments — inv_adj_line.cost is 0 |
02 § inv_adj_line.cost is 0 — use inv_tran.unit_cost_amt; net both directions |
| Show the label of a user-defined dropdown value | 02 § User-defined dropdown labels — extensibility_dropdown_value, not code_p21 |
A column you're sure exists 404s (qty_completed, line_number, prod_order_hdr.location_id) |
02 § Column names that trip queries |
| Kit / assembly margin looks negative, or kit cost is counted twice | 02 § Kit and assembly lines — parent commission_cost is pre-rebate; invoice_line_uid_parent ≠ 0 marks component lines |
Create / update records (Transaction API)
| Task | Where |
|---|---|
| Payload anatomy (TransactionSet / DataElements / Edits) | 03 § Request Structure |
| Payload rejected / values not landing (shape & type mistakes) | 03 § Payload Anatomy |
HTTP 200 with Summary 0/0/0 and nothing written |
03 § Common mistakes — the body is wrapped in "TransactionSet" or starts at the transaction |
Edit the header of a completed PO (Save is not enabled.) |
03 § Editing a Completed PO |
| Clear a field, or write NULL | 03 § Edit fields — a blank stores NULL, an empty string or nothing, depending on the field; read it back |
| Add a price book to a pricing library at a position | 03 § StrategicPricingLibrary Service |
| Validate a payload offline before posting (JSON or XML) | scripts/validate_payload.py · 03 § Payload Anatomy |
| Copy-ready payload files (JSON and XML, validator-verified) | examples/payloads/ |
| Send/receive XML instead of JSON | 03 § XML Payloads |
| Get a service's schema, template, defaults | 03 § Endpoints · committed full-field JSON in definitions/ |
Update an existing record (Status "New" + keys; "Existing" is broken) |
03 § Updating an Existing Contract |
| Insert new keyed rows (upsert) + one-tx-per-POST rule | 03 § Upsert Semantics |
| Two lines came back as one — same item twice, only the last quantity landed | 03 § Keys — Row Identity and the Collapse Trap |
Pick the right Keys for an element (and what over-keying breaks) |
03 § Choosing a key · 03 § What the definition already tells you |
| Child rows all attached to the last parent (extended info, lots, breaks) | 03 § The general rule: repeat the element pair |
| Read one record whole / clone an existing record | 03 § Reading One Record |
Read several records in one /transaction/get call (no field/element subsetting exists) |
03 § Reading several records in one call |
Failed: 1 but part of my batch landed |
03 § What Failed actually guarantees — a Transaction is atomic; Transactions in one POST are not |
| A failed create still left an order behind / an order number was skipped | 03 § A failed create leaves rows behind — soft-deleted header and lines, number consumed; filter delete_flag = 'N' |
Append many lines to a saved order · cancel an order · release a Hold · Ambiguous data detected / 'X' has been deleted |
03 § Order Service — Line Appends, Cancels, Holds, and Item Lookup · runnable: order_append_hold_cancel.py / OrderAppendHoldCancel.cs |
| Order with hundreds of lines is slow — split it | 09 § Large Sales Orders: Split at About 100 Lines |
| Create a purchase order | 03 § PurchaseOrder Service |
| Create a direct-ship PO from a sales order (drive a wizard) | 04 § Driving an In-Window Wizard — needs a Buyer ID; commits at cb_next |
| Change the salesrep on an order or quote | 03 § Order Service — Reassigning the Salesrep — TP_SALESREPS.tp_salesreps; oe_hdr has no salesrep column |
Transaction Failed with no reason on the transaction |
03 § Failure detail is in the top-level Messages |
| "may currently be edited by USER" — write refused | 03 § oe_hdr.completed = 'T' — pre-screen WHERE completed <> 'T'; usually an abandoned lock |
| Telling quotes from live orders | 02 § Quotes are projected_order = 'Y' — quote_type is NULL everywhere |
What a PO type letter means (B is buyer-keyed, not backorder) · where a PO came from (source_type) · how a deleted PO looks |
03 § Purchase Order Types |
| "When did the supplier say this would ship?" | 02 § po_line.supplier_ship_date — not a promise on direct-ship POs; use date_due |
Peg a PO line to the sales order it was bought for (oe_line_po) |
02 § oe_line_po holds stale links — join on both key pairs and filter delete_flag; a deleted PO leaves its sales lines open with nothing on order |
| Convert an existing order line to direct ship — docs said it was refused | recipe: order-line-to-direct-ship · 04 § Changing a saved line to direct ship — the gate is allocated stock, not the saved state: set TP_EXTDINFO.extd_info.allocated_qty = 0, then disposition = D; /transaction also needs po_cost |
Confirm a direct ship (DirectShipConfirmation) |
04 § The Interactive path works (runnable: direct_ship_confirmation.py) · 04 § The Transaction API path works on 26.1.5950.0 — one POST keyed by po_no · 04 § DirectShipConfirmation writes its ship date down — overwrites supplier_ship_date on every line; checkboxes take ON/OFF |
| Receive a PO / receipt won't post (bin errors) | 03 § PurchaseOrderReceipt Service |
| Vouch a receipt against a vendor invoice | 03 § ConvertPOToVoucher Service |
Column is disabled for my service account but not for a person |
03 § Column is disabled Can Mean "Disabled For You" — role/DynaChange-scoped; test with the account that will run it |
Is the definition's Required flag trustworthy? |
03 § What Required actually means — no, wrong in both directions |
Sequence contains no matching element / Invalid column name on a keyed write |
03 § Choosing a key — every Keys field must be a real column and be sent in Edits |
| Write a note on an order (header/line) | 04 § Sales Order Notepad Writes · 04 § PurchaseOrder Notepad Writes — Interactive only; /transaction refuses (why) |
Write a note on a production order (note_type_cd 2758) |
04 § Production Order Notes — Interactive only; /transaction reports success and writes nothing (why) |
| Write an item / customer / supplier note | 03 § Commands Endpoint — one stateless POST · or 04 § Standalone Notepad Windows, where the mandatory area picker is cb_selectall via /tools |
| Call a commands-only service (notepads, reverse payment, slab adjustment) | 03 § Commands Endpoint — request shape and Action codes |
| Get an async callback instead of polling | 03 § Callbacks instead of polling |
| Fetch one record by key over OData | 02 § The other OData surface — /data/erp/views/v1/{view}('key') |
| Consumer key 401s on a table you just granted it | 00 § The OData allow-list is baked into the token — scope is fixed at issue time and the tokens last ~20 years; mint a new one. users never works, whatever the scope (02) |
| Which OData version / what's supported | 02 § Protocol version · 02 § What the service supports — v4; no server-driven paging |
| Does my server have endpoint X? | 05 § Discovering what your tenant actually exposes — apiref.aspx + per-family /help |
| Valid P21 login rejected by the middleware admin site | 00 § Application Security settings — needs Access to SOA Admin Page |
| Attribute API writes to a real user, not the service account | 00 § Attributing writes to a real user |
| Who can read a consumer key's value (DynaChange rules) | 00 § Method 2: Consumer Key — RuleState.ConsumerKey exposes it to any rule author |
| Update a specific order line deterministically (line handles) | 03 § Design for updates: assign your own line handles · recipe: update-order-lines |
You cannot retrieve an RMA from the Order Entry/Front Counter window |
03 § RMA Service — use the RMA service; route on oe_hdr.rma_flag |
A tool is missing from GET /v2/tools on a multi-tab window |
04 § Sales Order Notepad Writes — tool lists are tab-scoped and accumulate; select the tab first |
Which discovery endpoint — definition vs defaults vs basics |
03 § Endpoints |
Write through disabled columns/tabs (IgnoreDisabled) |
03 § IgnoreDisabled — not a universal unlock; it can report success and write nothing |
| Contract break tiers refuse to save / "Tab page is disabled" | 03 § VALUES Writes Are Refused on 26.1 · 14 § entry 8 |
| Field order silently changing values | 03 § Field Order Matters |
Labels vs code_no (UseCodeValues, code_p21) |
03 § UseCodeValues |
| Check success properly (HTTP 200 lies; per-tx pass/fail) | 03 § Response Format · 06 § Transaction API Errors |
| Read a record back / verify a write | 03 § Endpoints — /transaction/get |
Long-running / async transactions (no cancel path once queued; status 2 ≠ success — outcome is a JSON string inside Messages) |
03 § Async Operations |
| DynaChange rules & popup suppression for the API user | 03 § DynaChange and Popup Handling |
| Run a stored procedure via API | 03 § Stored Procedure Executor |
By record type
| Task | Recipe | Manual |
|---|---|---|
| Create a sales order + gotchas (source_loc_id, dates, DynaChange) | create-sales-order | 03 § Create Order · 03 § Order Service Gotchas |
| Order with an assembly line (explode / spawn prod order) | order-with-assembly | 04 § Sales Order Entry with Assembly Lines |
| Job contract: create, lines, breaks | — | 03 § JobContractPricing Service |
| Job contract: update / add lines / commission costs | update-contract-lines | 03 § Updating an Existing Contract · 03 § Upsert Semantics · 03 § Commission Costs |
| Job contract: bin quantities | edit-contract-bins | 03 § Editing Bin Quantities (Interactive fallback: 04 § Tab Unlock Sequences) |
| Job contract: attach a ship-to (pricing follows the ship-to grid, not the header) | — | 03 § Attaching a ship-to · runnable: contract_shipto_line_no.py / ContractShipToLineNo.cs |
Job contract: update one of two lines with the same item_id (select by line_no; no end_date, no header re-save) |
— | 03 § Commission Costs · 03 § Updating an Existing Contract · runnable: contract_shipto_line_no.py update-line |
| Job contract: edit an expired contract / "You may not change the end date of this record" | update-contract-lines | 03 § Updating an Existing Contract — omit end_date unless you are changing it |
Job contract: how cost settings reach the order and invoice (binding at entry, commission Value vs Order vs Source, rebate prerequisites, duplicate item_id lines) |
rebate-assembly-program | 03 § How Contract Costs Reach the Order and the Invoice |
Rebates on assembled parents — production orders (linked or build-to-stock) and kits; which vendor gets the rebate (vendor_rebate.vendor_id = oe_line.supplier_id) |
rebate-assembly-program | 03 § Rebates on assembled parents |
A fixed per-unit rebate on a contract line (other cost Source / Average Cost / Difference, positive value) |
rebate-assembly-program | 03 § A fixed per-unit rebate |
| GL entries of a rebated invoice (Sales Journal at moving average, Rebate Journal pair) | — | 03 § The GL flow of a contract rebate |
| Assembly / BOM definition — create (Transaction on a plain item, Interactive when the item is already a component) and edit | new-assembled-part | 03 § Part + Assembly Creation Workflow · 03 § Assembly Service · runnable: assembly_create_edit.py / AssemblyCreateEdit.cs |
Assembly create: production_order_processing came back ON / Column is disabled: item_desc |
— | 03 § Defaults and Disabled Fields on Create |
| Item: primary bin / primary supplier at a location | set-primary-bin-supplier | 03 § Item Service |
Create a brand-new item — Item service alone 500s with ...tabpage: TABPAGE_2 or A Default Sales Unit... Must Each Be Specified |
— | 03 § Item Service Gotchas — use 11 § Minimum Create Payload instead |
| Inventory REST create 500s — what does the error actually mean, is anything left behind | — | 11 § An incomplete create payload 500s — message names the missing block; nothing persists on failure |
Soft-delete an item (inv_mast.delete_flag) idempotently — and why a second write of any kind then fails |
— | 03 § Item Service Gotchas — a deleted item blocks every further Item-service write, not just a repeated delete; check delete_flag over OData first |
Item: add a supplier to a location's list (the flip's prerequisite — both keys + IgnoreDisabled) |
set-primary-bin-supplier | 03 § Adding a Location Supplier Row |
Item: enable bin tracking at a location (track_bins, and the bin it auto-assigns) |
— | 03 § Enabling Bin Tracking |
| Item: change a location's product group without losing its GL / revenue / COS accounts | — | 03 § Changing a Location's Product Group Re-Syncs Its GL Accounts — the move resets the accounts to the target group's defaults; snapshot before, restore after |
Item: set the header default product group (inv_mast.default_product_group) |
— | 03 § Item Service Gotchas · 11 § Known Limitations — no API write path; the location product group is writable |
Merge two items or two customers (m_itemmerge / m_customermerge) — and what history moves, changes, or is lost |
— | 03 § Item Merge and Customer Merge — customer merge reports Failed: 1 when it succeeds; merged item IDs resolve through alternate codes · runnable: item_customer_merge.py / ItemCustomerMerge.cs |
| Create warehouse bins | create-bins | 03 § BinLocation Service |
Lock, unlock or delete bins (a skipped delete reports no error — read back through the p21_view_bin OData view) |
— | 03 § Locking, Unlocking, and Deleting Bins · runnable: bin_lock_delete.py / BinLockDelete.cs |
Create a new warehouse/location — a specific id is refused, a blank one false-succeeds at location_id=0 |
— | 03 § Location Service — the fix is Keys: [] with no id at all; P21 assigns a real number |
Location: update or soft-delete an existing warehouse — company_id becomes Column is disabled after create |
— | 03 § Location Service — Updating |
| Sales price pages (codes, breaks, field order) | — | 08 SalesPricePage Codes · 08 § Transaction API Alternative |
| Purchase-side pricing pages (supplier / item / discount group) | — | 08 § Purchase-Side Pricing Services |
Create a sales price page through the Transaction API + link it to a price book (USED BY cannot link) |
— | 08 § Creating a Page Through the Transaction API · runnable: sales_price_page_create_link.py |
| Edit a supplier/item purchase page in place (exact keys, full field set, labels not codes) | — | 08 § Editing a Supplier/Item Purchase Page · runnable: purchase_pricing_page_edit.py |
Break fields named differently per service (calculation_value1 vs value1) |
— | 08 § Cross-Service Break-Field Names |
What a stored price_page_type_cd means (212 = Item) — and why a code from one column's table can't decode another |
— | 08 § Price Page Type |
| Set a carrier tracking number on a pick ticket (and why not after invoicing) | — | 03 § Shipping Service — Carrier Tracking Number |
Delete a pick ticket — Record specified was not found on an orphaned ticket |
— | 03 § Shipping Service — Deleting a Pick Ticket — tickets left on completed orders cannot be reached by the API or the client |
| Reassign a salesrep (customer + ship-to) | reassign-salesrep | 03 § Payload Anatomy |
Turn on Job/Contract Pricing for a customer — Column is disabled: job_pricing (use_vendor_contracts_flag first) |
— | 03 § Customer Service — Turning On Job/Contract Pricing |
Remove a rep from a customer's grid (no delete_flag — it's row_status_flag: "Delete") |
reassign-salesrep | 03 § Customer Service — Removing a Salesrep Grid Row |
A grid row looks undeletable — no delete_flag in the definition |
— | 03 § Read ValidValues before assuming a grid can't delete |
Edit a salesrep's name/email (contact_id key; email on TABPAGE_2) |
— | 03 § Salesrep Service |
Update a supplier's contact details (central email/phone live on Address, not Supplier) |
update-supplier-contact | 03 § Salesrep Service |
| Modify an existing sales order (edit a line in place, add a line) | update-order-lines | 03 § Design for updates: line handles |
| Item Defaults per location (required before location-appends on fresh locations) | — | 03 § ItemDefaults Service |
| Create bin zones (fresh-location prereq for bins) | — | 03 § PutawayZone / PickZone Services |
| Create a customer (salesrep_id + default_branch gotchas, no zip→rep cascade) | create-customer | 03 § Common Services |
Create a ship-to on an existing customer (element map, UseCodeValues: true, inherited rep) |
create-ship-to | 03 § ShipTo Service — Creating a Ship-To |
Create a requisition PO (po_type 'R'; disabled po_hdr_po_type; vendor vs supplier) |
create-requisition-po | 03 § Purchase Order Types |
| GL dimensions via API (voucher services carry them; POs don't) | — | 03 § GL Dimensions in the API |
| Create a vendor (and its auto-created supplier; ID rule; rebate tracking) | rebate-assembly-program | 03 § Vendor Service · runnable: vendor_create.py / VendorCreate.cs |
Change an item's average cost / write residual cost down (InventoryCostEdit) |
inventory-cost-edit | 03 § InventoryCostEdit Service |
| Dispose of zero-value stock on a $0 order | dispose-zero-value-stock | 03 § Order Service — Line Appends |
| Stock PO → receipt → voucher, and drop ship end to end | purchase-receive-vouch | 03 § PurchaseOrder · 04 § Driving an In-Window Wizard |
| Sales order → pick ticket → invoice | sales-order-to-invoice | 03 § Create Order · 12 § Shipping and Invoicing |
| Customers / vendors / contacts / addresses (simple CRUD) | — | 05 § CRUD Operations |
Read/create sales orders via REST (/api/sales/orders) |
— | 05 § Other REST Endpoint Families · 05 § Creating an Order — trailing slash required; lines nest under Lines.list |
Filter /api/sales/orders ($query) or get an order's lines over REST |
— | 05 § /api/sales/orders — PascalCase names (OrderNo); extendedproperties=Lines |
REST-family read returns POLines / Lines as null |
— | 15 § Child collections — add extendedproperties |
Price many items through /api/ecommerce (XBG GetItemPrice) |
— | 05 § api/ecommerce — StoreName is the company ID; unknown items still return Result 0 |
Price or check availability for an item ID containing / |
— | 11 § URL Encoding — v2/parts/v2/price/{id} with %2F, v2/parts/availability?itemId=, or parts/?$query=ItemId eq '{id}' to read the part |
Write Clippership tracking rows, or count rows on /data/erp/views/v1 |
— | 02 § /data/manifest/v1 · 02 § Documented limitations ($inlinecount=allpages) |
| Inventory items: read / create / update locations | — | 11 § Reading Items · 11 § Minimum Create Payload · 11 § Updating Existing Location Fields |
Update a supplier's cost or list price over REST (Suppliers.list[].Cost / ListPrice; restamps every inv_loc) |
— | 11 § Updating a Supplier's Cost · runnable (ListPrice + Stockable): inventory_update_list_price_stockable.py |
| Clone an existing item under a new ID (REST GET → strip → POST) | — | 11 § Cloning an Existing Item |
REST create or location append fails Required value missing for Revenue Account · which DivisionId to send |
— | 11 § GL accounts when the location has no defaults · 11 § DivisionId is often the supplier ID |
| Append locations at scale (supplier-x-loc primary, GL requireds, kit Buy, OP/OQ, retries, PrimaryBin, discount groups from location defaults) | — | 11 § Location-Append & Update Gotchas |
| Customer-specific price + availability lookup | — | 11 § Pricing Endpoints |
| Batch price + availability lookup (multiple items in one call, JSON or XML) | — | 11 § Batch Pricing |
| User-defined tables (UDT) rows | — | 13 § Insert · 13 § Update · 13 § Delete — update/delete select a row by primary key with {"rowuid": N}; a name/value condition is a 400 or a silent no-op |
| UDT delete "succeeds" but nothing is deleted | — | 06 § [0] rows deleted · 14 § entry 7 |
| Bulk-load a UDT from CSV (2026.1+) | — | 13 § Bulk Data API — CSV upload; headerless file silently inserts nothing |
Drive a window (Interactive API)
| Task | Where |
|---|---|
| Session → window → change → save lifecycle | 04 § Session Lifecycle |
| v2 payload shapes (save body is the bare GUID, etc.) | 04 § v1 vs v2 API Differences |
| Find field / tab / datawindow names | 04 § Finding Field Names · 04 § Window Discovery |
Read a window's SQL — the datawindow's full SELECT, its column → table.column map, and one field's table, datatype, and update table |
04 § P21 SQL Information · recipe: window-sql-information — run m_sqlinformation with POST /v2/tools; the SELECT comes back only from POST ui/full/v2/window/page {"PageName": "SQL"} on the dialog, as TextBoxes[].Text |
Which table does this field write? — an API field name that is not a database column (sales_loc_id, buyer_id) |
16 Window Field-to-Table Maps — 228 datawindows in 7 windows, each field resolved to table.column; files in definitions/window-maps/ |
| An action works in the client but not the API — capture the web client's request and replay it | 04 § Browser DevTools — the web client calls ui/full, not /api/ui/interactive; worked example: selecting one row (ui/full/v2/data/select) |
Open a window (use ServiceName — Name/Title can 400) · map window→service |
04 § Open Window · 04 § Window→Service Discovery |
The window has no service name (frame_menu.service_name NULL) — drive it by menu class instead |
04 § The ui/full Surface |
| See which windows an Interactive session holds, keep it alive, or end one by ID | 04 § Session Management — sessions/state, sessions/ping, api/ui/admin/session/{id} |
| v1 Interactive change returns 404 or changes nothing | 04 § Data Operations (v1) — use v2; the v1 path takes only the v2 body |
| Create a contact on an existing customer's address | 03 § Contact Service — send address_id |
| "Cannot open window ** ** because is not available" — the name came back blank | 04 § Open Window — blank means the ServiceName never resolved |
| Handle popups / response windows (Status 3, windowopened) | 04 § Response Windows · 04 § Response Window Types |
| Answer a rule-callback dialog ("Item Issues Detected") | 04 § Worked Example — but fix the data first: 03 § Root Cause and Data Fix |
| Enable, disable or re-scope a DynaChange business rule through the API | 04 § Managing Business Rules Through the DynaChange Organizer — the dc.organizer.rule tool on any open window · runnable: dynachange_rule_toggle.py |
Interactive order saved as a quote / disposition looks disabled / wizard made an RFQ · build a direct-ship PO |
04 § Set quote to OFF first · runnable: order_entry_quote_off.py |
| Create an item in the Item window (unit flags, one location per session) | 04 § Creating an Item in the Item Window — Units of Measure grid can be locked (Column is disabled: unit_id) by a web-client screen version assigned to your role — cause, fix and check query; or use the Inventory REST create |
Attach a document to an item, assembly or kit (Document Links) — Transaction API says Column is disabled: link_name |
recipe: item-document-links — upload with the file handler, add through the Item window's m_addlink dialog (one display area at a time via ui/full/v2/data/select + cb_select, or all with cb_selectall), save · 04 § Adding a Document Link in the Item Window |
Add a price library to a customer · library order (sequence_number) |
04 § Customer Window: Adding a Price Library — edit order decides the sequence · runnable: customer_price_library.py |
Answer a w_message dialog — it's fully controllable, not just auto-answered |
04 § Worked Example: w_message |
Fill fields in a popup (TabName: null) |
04 § Response Window Handling (Tabless) |
Buttons / tools (?windowId=, not ?id=) |
04 § Running Tools |
| Unlock a disabled tab | 04 § Tab Unlock Sequences |
| Row selection traps (sync bug, detail-form rebind, row 0) | 04 § Known Issues and Workarounds |
| Key field silently swallowing later edits | 04 § Key Fields Commit the Cursor |
| Verify a save actually persisted | 04 § Verifying Writes |
| PO notepad notes (header vs line) | 04 § PurchaseOrder Notepad Writes |
| Bulk/batch interactive work (session reuse, error recovery) | 09 Batch Processing Patterns |
| Intermittent "Unexpected Response Window" in production | 07 Session Pool Troubleshooting |
Production & manufacturing
| Task | Where |
|---|---|
| Service catalog & schemas (ProductionOrder, TimeEntry, …) | 12 § Available Services |
| Assembly behavior flags (prod-order vs kit vs build-to-stock) | 12 § Assembly Behavior Flags |
| Link an existing production order to a sales-order line — docs said the API could not | recipe: link-production-order · 12 § Production Order Gotchas — Interactive: the grid's right-click tool m_retrieveotheropentransactions, tick assigned, save; the hidden retrieve_open_transactions column is a decoy |
| Full runbook: create → print → confirm → complete → ship | recipe: production-order-runbook · 12 § Production Order Lifecycle |
Pick ticket won't generate (make loc vs stock loc) · sales order: m_picktickets returns HTTP 200 [] |
12 § Printing the Pick Ticket · 03 § m_picktickets example · sales order: recipe § Eligibility traps — omitted date ranges take past-dated defaults; an under-allocated kit skips the whole order |
| Confirm a pick (shell-confirm trap — use Interactive) | 12 § Confirming the Pick |
| Complete / production receipt (+ per-component cost override) | 12 § Completing the Production Order |
| Record labor hours | recipe: record-labor-time · 12 § Recording Labor Hours · 12 § Time Entry Against a Production Order |
| Ship + invoice | 12 § Shipping and Invoicing |
| Inventory write-off / adjustment | recipe: inventory-adjustment · 12 § Inventory Adjustment |
| Why COGS doesn't match the receipt | 12 § Cost Model |
Report labor actuals — who did the work (service_technician_uid, not created_by), no work-performed date |
12 § Reading Labor Actuals |
Make vs parts location, UD fields hidden from the API role, cancel a pick ticket, qty_to_complete pre-fill, reprints, linking an existing order to a sales line |
12 § Production Order Gotchas (Field-Verified) |
| New assembled part end to end (item → BOM → contract line → first production order) | recipe: new-assembled-part |
Documents & reports (PDF)
| Task | Where |
|---|---|
Generate any m_* report as PDF |
recipe: generate-pick-ticket-pdf · 03 § PDF Report Generation |
Invoice PDF (m_invoices) returns an empty HTTP 500 |
03 § m_invoices needs the API print flag |
| Print or reprint a PO PDF — empty 500 on every attempt | 03 § reprint_flag must match the PO's po_hdr.printed — read printed over OData and send it; never hardcode Y |
| Discover callable report names (hidden from /services) | 03 § PDF Report Generation (Discovery note — includes the right-click SQL Help path) |
Empty HTTP 500 from pdfreport on a Crystal report window (TABPAGE_1.tp_1_dw_1 vs CRITERIA.criteria) |
03 § The DataElement name comes from the definition |
| Report returns nothing / runs forever / works for one user only | 03 § Report windows are still windows — permissions, DynaChange date caps, cancelling the request does not stop the run |
| Production pick ticket at a specific location | 03 § m_picktickets example |
Generate a sales-order pick ticket with m_picktickets — HTTP 200 [], no ticket |
recipe § Sales-order pick ticket — "Sales Order" with UseCodeValues: false, all three date ranges sent wide, read pick_ticket_no from oe_pick_ticket · § Eligibility traps |
Reprint a direct-ship pick ticket — m_reprintpicktickets returned HTTP 200 [] for every direct-ship ticket tested |
recipe § Direct-ship pick tickets do not reprint — known limitation, most likely a report-side exclusion; no API workaround found |
| PDFs from print flags on a normal transaction | 03 § PDFs from the /transaction endpoint |
| Merge several report PDFs — barcodes print as text | 03 § Merging Several Report PDFs into One File — rename the font-subset tags |
| Put an assembly's drawing on its pick ticket · does an API pick-ticket PDF include linked documents? · a linked document never prints | recipe: item-document-links — display area 3453 Sales Order Pick Ticket · § Printing the document with a form — the link must be Outside Use and the location must send outside-use documents to print (ship-to for the packing list); the PDF is the form alone, and the files come back as DocumentAssociations / filerequested jobs |
When something breaks
| Task | Where |
|---|---|
| Upgrading P21? What breaks between versions | 14 Breaking Changes — registry re-run unchanged on 26.1.5950.0; 2026.1 (sequential fail-fast batched changes, silent-false-success loads, IgnoreDisabled, bad-DatawindowName poison) · resolved in 5940.0 (Accept-header 500, ghost sessions) · 25.2 (DatawindowName) |
| Empty HTTP 500, no body, on an interactive call | 14 § entry 9 — a bad DatawindowName on the previous request; not the Accept header on current builds |
| What user-defined fields exist in this P21? | 15 § extensibility/userdefinedfields — every UDF with type metadata, grouped by table; {tableId} is the *_ud name, not the base table |
| Read a whole GL journal entry in one call | 15 § accounting/gl — GET /api/accounting/gl/{transactionNumber} returns every line, balanced; Source walks back to the originating document |
| Create or update a CRM task / activity | 15 § sales/tasks — verified round trip; "Customer ID is required" means LinkId |
| A REST family's list route hangs or returns tens of MB | 15 § The bare collection GET is an unbounded full-table dump — no paging, and $top is silently ignored; key off OData instead |
| Post a balanced GL entry over REST | 15 § accounting/gl § Posting a journal entry — POST /api/accounting/gl/ takes an array of lines; an unbalanced batch is refused server-side with "Records do not balance" |
Create a PO over REST and the price comes back 0.00 |
15 § purchasing/purchaseorders § Creating a PO — UnitPrice is not honored on create, PriceEdit: "N" routes it through pricing lookup instead; use the Transaction API PurchaseOrder service when the price must be exact |
PO create fails Buyer ID... Invalid buyer ID / an unmatched Sales/Production/PO Intersection |
15 § purchasing/purchaseorders § Creating a PO — BuyerId empty is the real cause of both messages; the create is built on P21's Import/Export engine, hence the escaped XML error body |
createWmsAdjustment fails Bin is required or can't find the adjustment reason |
15 § inventory/inventoryadjustments § createWmsAdjustment — binCd is mandatory on a bin-tracked location; reason must name a currently-active reason record, not a historical one |
GET /odataservice/odata/table/bin (or /view/bin) 404s with an HTML page, not JSON |
02 § One object name is unreachable regardless of surface: bin — likely an IIS routing collision with the reserved bin/ folder name; read inv_loc.primary_bin or bin_ud instead |
| Upload/download/delete a real file over REST | 15 § filehandler — full round trip verified; this is a real network file share, not a sandbox |
Read the middleware's build/config without the serverinfo XML trap |
15 § environment/systems — JSON directly; no /ping on this family, and needs a trailing slash in C# |
| Move stock bin-to-bin without changing on-hand quantity | 15 § inventorymovement — Y/N not booleans, toBin must already be a real association, "success" doesn't guarantee a nonzero move |
| Stage a cycle count without touching live on-hand | 15 § externalcounts — ItemId must be repeated on the bin sub-record, not just the line |
| Resolve a scan term / barcode / customer part number to an item | 15 § partscan — Source names which identifier matched |
| Set a customer's invoice/RMA/packing-list report template | 15 § customerformtemplates — PUT is not a blind upsert; a null uid against an existing row fails "data already exists" |
| A "service order" REST family that looks like it should be separate from Order Entry | 15 § service/serviceorders — it resolves against any oe_hdr.order_no; the family is a view, not a distinct object |
sales/opportunities / consignmentusageorders / exchangerates create fails with a confusing error |
15 §§ each family — check the tenant's own lookup tables (opportunity status/stage/type/step, a consignment contract, a second currency) before assuming the API is broken |
| Which REST endpoints/families does my tenant actually have? | 05 § Discovering what your tenant actually exposes — /docs/apiref.aspx lists every family (takes a bearer token); confirm one with /help, never /ping. Plus /api/v2/services (Transaction) and the OData service document |
Token call returns HTTP 500 Unable to generate client token. |
06 § Token Endpoint Errors — the consumer key is not registered on that tenant; read InnerException, not ErrorMessage. Common after a test-tenant refresh from production (00) |
Status: "Existing" / "Update" rejected on a transaction |
03 § Status: "New" is the only value — one-member enum; 400 on 5940.0 and later, 500 on older builds |
| Which .NET runtime do P21 business rules need? | 14 § The middleware runtime — both .NET and .NET Framework, for now |
| Which middleware build am I on? | 14 § Reading the middleware version — no version endpoint; it rides the session-create response |
| Error catalog by API | 06 Error Handling (per-API sections) |
| Write refused for being too long / how long is a field? | 06 § Field Length Limits — the API exposes no lengths; measured values + how to measure safely |
| Quick symptom → cause table | 06 § Common Issues Quick Reference |
Invalid column name: {field} — is the capability really missing? |
06 § Common Transaction Errors — sometimes the sibling field does the job; check ValidValues |
Invalid {field} value: 700 on an enum |
06 § Common Transaction Errors — send the label, not the code_p21 integer |
OData 404 saying Edm.Decimal / Edm.String |
06 § Incompatible Operand Types — numeric key column, drop the quotes |
| Auth failures | 06 § Authentication Errors |
401 right after a token call that worked (Authorization header was not present) |
06 § 401 Authorization header was not present — a 307 stripped the header; send the trailing-slash router URL |
| Intermittent interactive failures under load | 07 Session Pool Troubleshooting |
may currently be edited by <your API user> · Transaction fails while an Interactive session is open · order stuck after a crash |
06 § Locks and orphaned sessions |
| What changed in these docs recently | 10 Changelog |
Doc inventory (what each file is)
| Doc | Scope | Size (line counts as of 2026-10-10) |
|---|---|---|
| 00-Authentication | Tokens (v2/consumer key), permissions, UI server URL | large (~1,500 lines) |
| 01-API-Selection-Guide | Which API for which job | small (~400 lines) — read whole |
| 02-OData-API | Read-only queries | large (~1,900 lines) |
| 03-Transaction-API | Stateless create/update + service reference + PDF reports | very large (~5,700 lines) — use anchors |
| 04-Interactive-API | Stateful window driving, popups, traps | very large (~4,300 lines) — use anchors |
| 05-Entity-API | REST CRUD on 4 entities | very large (~4,700 lines) — use anchors |
| 06-Error-Handling | Errors across all APIs | large (~1,400 lines) |
| 07-Session-Pool-Troubleshooting | One deep-dive: session pool contamination | small (~500 lines) — single topic, read whole |
| 08-SalesPricePage-Codes | SalesPricePage field codes/order | very large (~2,800 lines) — single service, use anchors |
| 09-Batch-Processing-Patterns | Interactive bulk patterns + async client | very large (~5,400 lines) — use anchors |
| 10-Changelog | Doc change history | medium (~800 lines) — newest first |
| 11-Inventory-REST-API | /api/inventory/parts read/append/update |
very large (~2,700 lines) — use anchors |
| 12-Production-Labor-API | Production services + end-to-end lifecycle | large (~1,100 lines) |
| 13-UDT-Service-API | User-defined table CRUD | large (~1,800 lines) |
| 14-Breaking-Changes | P21 version breaking-change registry (check before upgrading) | small (~500 lines) — read whole |
| 15-Other-REST-Families | Sixteen more REST families: UDF metadata, GL, CRM tasks/opportunities/consignment orders, PO headers, inventory (adjustments/movement/counts/scan/serial info), file storage, system info, customer form templates, service orders, exchange rates | large (~1,100 lines) |
definitions/ |
Full-field service definition JSONs (every DataElement, field, key, label + payload template) | load one file per service |