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 |
| 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 |
| 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 |
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 |
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 |
| 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 |
| 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 |
| "When did the supplier say this would ship?" | 02 § po_line.supplier_ship_date — not a promise on direct-ship POs; use date_due |
Confirm a direct ship (DirectShipConfirmation) |
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 |
| 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) |
| Assembly / BOM definition | — | 03 § Assembly Service |
| 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 |
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 |
| Create warehouse bins | create-bins | 03 § BinLocation Service |
| 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 |
Break fields named differently per service (calculation_value1 vs value1) |
— | 08 § Cross-Service Break-Field Names |
| Set a carrier tracking number on a pick ticket (and why not after invoicing) | — | 03 § Shipping Service — Carrier Tracking Number |
| Reassign a salesrep (customer + ship-to) | reassign-salesrep | 03 § Payload Anatomy |
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 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 |
| 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 |
| Inventory items: read / create / update locations | — | 11 § Reading Items · 11 § Minimum Create Payload · 11 § Updating Existing Location Fields |
| Append locations at scale (supplier-x-loc primary, GL requireds, kit Buy, OP/OQ, retries, PrimaryBin) | — | 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 need a row_uid column; 2026.1-created UDTs don't have one |
| 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 |
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 |
| "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 |
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 |
| 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) | 12 § Printing the Pick Ticket · 03 § m_picktickets example |
| 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 |
Documents & reports (PDF)
| Task | Where |
|---|---|
Generate any m_* report as PDF |
recipe: generate-pick-ticket-pdf · 03 § PDF Report Generation |
| Discover callable report names (hidden from /services) | 03 § PDF Report Generation (Discovery note — includes the right-click SQL Help path) |
| 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 |
| PDFs from print flags on a normal transaction | 03 § PDFs from the /transaction endpoint |
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 |
| What changed in these docs recently | 10 Changelog |
Doc inventory (what each file is)
| Doc | Scope | Size |
|---|---|---|
| 00-Authentication | Tokens (v2/consumer key), permissions, UI server URL | large |
| 01-API-Selection-Guide | Which API for which job | small — read whole |
| 02-OData-API | Read-only queries | large |
| 03-Transaction-API | Stateless create/update + service reference + PDF reports | very large — use anchors |
| 04-Interactive-API | Stateful window driving, popups, traps | very large — use anchors |
| 05-Entity-API | REST CRUD on 4 entities | medium |
| 06-Error-Handling | Errors across all APIs | large |
| 07-Session-Pool-Troubleshooting | One deep-dive: session pool contamination | medium — single topic |
| 08-SalesPricePage-Codes | SalesPricePage field codes/order | medium — single service |
| 09-Batch-Processing-Patterns | Interactive bulk patterns + async client | very large |
| 10-Changelog | Doc change history | small |
| 11-Inventory-REST-API | /api/inventory/parts read/append/update |
large |
| 12-Production-Labor-API | Production services + end-to-end lifecycle | large |
| 13-UDT-Service-API | User-defined table CRUD | large |
| 14-Breaking-Changes | P21 version breaking-change registry (check before upgrading) | small — 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 |
definitions/ |
Full-field service definition JSONs (every DataElement, field, key, label + payload template) | load one file per service |