Video chapters
- AppEngine External API Integration and Data Mapping
- Choose the AppEngine action before writing the adapter
- Account, organization and staff session
- Check installed providers and the external contract
- Extend the product with Supplier SKU
- Map the supplier notice into a pending intake
- Prepare location, products and exact mapping
- Run the server adapter and inspect the pending record
- Find the imported delivery in Studio
- Receive the restock in the actual Inventory screen
- Verify stock, bill and the posted journal
- Repeat and recover without duplicating the delivery
- Verify repeated import and repeated receive
- Reconcile a lost response before retrying a write
- Validate input and recover from supplier failures
- Cancel an incorrect pending shipment
- Create the corrected intake and retain provenance
- Deploy the adapter with a clear synchronization policy
- Follow the shipment through its complete AppEngine outcome
Written guide
Companion source code
Get the course source code on GitHub. The folder includes the supplied examples and setup instructions. Keep credentials in your own environment; use the maintained API reference when adapting recorded request examples.
← AppEngine course catalogue · Watch the course · Download runnable examples and observed JSON
Purpose: connect an external shipment API to an existing AppEngine inventory process. By the end you can discover integration options, extend product mapping, import a delivery, receive it deliberately, verify stock and accounting, and recover without blindly duplicating records. This is an AppEngine use-case course, not a tutorial on generic HTTP programming.
The course uses a labelled supplier fixture because a commercial supplier is not required to learn this platform boundary. AppEngine requests, Studio actions, saved intakes, stock movements and journal results were executed in the isolated training organization. Production supplier authentication and payload formats must be adapted to that supplier's published contract.
Why the business action matters
Your adapter knows the supplier's payload and product codes. AppEngine knows the receiving location, product records, inventory movements and billing process. Keep that separation. Repository queries and custom fields find/extend existing records; the inventory-intake business action advances a delivery from pending to received. A raw quantity update does not reproduce the business action's costing, linked bill or traceability.
The same outside identifier has several representations: supplier shipment ID → namespaced intake reference; supplier SKU → your product data.supplierSku → your product data.sku; intake sk → receipt API; intake number → stock movement reference; bill ID → posted journal reference. Never substitute a display name for a required ID without checking the endpoint's lookup contract.

Discover the provider before writing an adapter
Call GET /upstream/integration-use-cases/Product%20Sync using your organization and staff session. The observed response lists installed provider classes, configuration schema and supported operation names. It does not say your organization has connected those accounts. If a suitable provider exists, follow Integrations and Webhooks: provider configuration. The teaching supplier has no built-in connector, so the adapter makes its outbound call privately, validates it, then calls the existing AppEngine action.

This is the runnable companion to the supplier integration course. The Northwind endpoint is a controlled teaching supplier. AppEngine is the real training backend. Nothing here pays a supplier, places a commercial order or sends a notification.
Start with the right platform details
Open https://studio.appmint.io/ and select your organization. The catalogue's AppEngine Introduction teaches account creation and obtaining your endpoint and organization ID. The Database & Repository APIs course's Staff sign-in chapter teaches POST /profile/signin. Authentication challenges/password changes are covered in the Authentication course. Complete them and obtain a current ordinary staff bearer with Inventory access; a platform root account is not required.
The exact starting references are Introduction: account, address, token and first request, staff password sign-in, and sign-in outcomes. Select the intended organization before obtaining the staff session; a challenge response is not a completed session. The supplier key authorizes the external service, while the staff session authorizes AppEngine. Neither goes in the browser.
Use your own isolated organization, products, receiving location and accounts. Do not copy the recorded organization or IDs as if they were your resources. The full HTTP API reference is at https://appengine.appmint.io/documentation. For self-hosted installations use your deployment's documentation address.
Copy .env.example to a private file and populate it on your server. Do not commit it. LOCATION_ID means the saved location's data.name, not its record sk. Keep supplier and AppEngine credentials separate. Use HTTPS for hosted connections.

Prepare the AppEngine records
- Create a receiving location through Business Locations. Record its exact saved name and currency.
- Prepare two existing
sf_productrecords. Save their actual product SKUs. - Read
GET /repository/collections/sf_product?enriched=false, select the definition withdata.name === 'sf_product', preserve its complete schema and add string propertysupplierSku, titleSupplier SKU. Save the complete definition throughPOST /repository/update/COLLECTION_SK. Read again and retain its current ID/version. - Populate
data.supplierSkuon the products usingPOST /repository/update-partial/sf_product/PRODUCT_SKwith the flat body{"data.supplierSku":"NW-OAK-24"}. UseNW-WAL-18for the second product. Do not replace the entire data object. POST /repository/find/sf_productwith{"query":{"data.supplierSku":"NW-OAK-24"},"options":{"page":1,"pageSize":2}}must return exactly one product. Do the same for walnut. The adapter refuses missing/ambiguous mappings.
The course uses SUPPLIER-OAK-1011 and SUPPLIER-WAL-1011 only as synthetic example SKUs. Its hidden products are not offered for sale. If you use other product SKUs the adapter discovers them from your supplierSku values.
Open the current contract before adapting the example
Use the maintained API reference to search the route names below. Select the method as well as the path; creating, reading and receiving an intake are different operations. Read that operation's parameters, response and error descriptions before adapting a recorded payload. The downloadable JSON is observed example evidence, not a replacement for the current reference.
| Decision | Maintained reference | What to carry into the adapter |
|---|---|---|
| Is there a suitable built-in provider? | Integration setup, then search /upstream/integration-use-cases | Provider operations and configuration requirements. Installed availability alone does not establish your selected account or authorize a supplier call. |
| Which product schema and records are being extended? | Collections and Repository | Preserve the existing definition; map external code to the product's actual SKU. A definition change does not populate record values. |
| Which delivery operation runs? | Search /storefront/inventory/intakes | Create a pending delivery, inspect its saved ID, then intentionally call receive. Retain the reference for reconciliation. |
| Did receipt finish the business outcome? | Receive operation and recorded stock/bill/journal readbacks below | Received stock, linked bill and posted journal are separate observations; journal posting may follow the HTTP response. |
This exercise is a direct supplier delivery, not a purchase-order receipt. Current AppEngine rejects purchaseOrderId on ordinary intake creation; a delivery against an existing purchase order begins through that order's Receive action. Likewise, existing approved reorder bills can be folded into a receipt rather than creating another amount owed. Our isolated demonstration did not have such prior commitments, so its one-new-bill result must not be generalized to every procurement state.
For a built-in integration, use Save the connection and Prove it works. Provider account selection, shared-service fallback and cost behavior are described in How integrations work; do not treat the discovery response as proof that a particular account is connected. The current course uses the labelled teaching supplier and makes no commercial provider call.
Open the downloaded workspace
Download the examples, extract the archive, then open a terminal inside its supplier-integration directory. The commands below run from that directory. JavaScript and Python adapters use their standard libraries. For TypeScript, run npm install before the compilation command below. Keep your populated environment and job-state directory private.


Start the teaching supplier
Node.js 20+ is sufficient:
node supplier-fixture.mjsIt binds to loopback port 4371, not a public service. In your private environment set SUPPLIER_URL=http://127.0.0.1:4371 and SUPPLIER_KEY=training-supplier-only. The local address is development configuration, not the hosted AppEngine address. The supplier key here is intentionally a non-secret fixture value. Your real provider's secret must remain private.
The fixture accepts GET /shipments/SHIPMENT_ID and returns two lines: 40 oak at 38.50 and 12 walnut at 61, currency USD, total 2272. Its response is not an AppEngine inventory record.

Run the full adapter in your language
Load your private environment first. Then choose one implementation:
node import-shipment.mjs MY-SHIPMENT-001
python3 import-shipment.py MY-SHIPMENT-001For TypeScript, install the included development dependencies and typecheck. Node 24 can execute the erasable types directly; on older Node use your normal TypeScript build/runtime:
npm install
npx tsc --noEmit
node import-shipment.ts MY-SHIPMENT-001Use the same ID when intentionally demonstrating reuse. Use a new supplier shipment identifier for a genuinely new shipment. Do not mint a new reference merely because a request timed out.
The adapter's sequence is: organization+shipment lock → exact existing active reference read → supplier request → full source validation → product mapping → persisted dispatch marker → pending-intake action → saved reference/ID. It returns created, reused, or reconciled. A supplier/API failure exits nonzero.
All three full adapters use the same platform contract. JavaScript, TypeScript and Python were actually executed against isolated AppEngine records; TypeScript also passed compilation. This example uses single-host directory coordination, not a distributed lock or universal exactly-once guarantee.

Receive intentionally, then verify separately
Import creates a pending delivery. Confirm what physically arrived first. Use Studio → Inventory → Intakes → search supplier reference → open intake → Receive → Receive stock. Or:
node client.mjs PUT "/storefront/inventory/intakes/YOUR_INTAKE_SK/receive"
python3 client.py GET "/storefront/inventory/sku/YOUR_PRODUCT_SKU"
sh client.sh GET "/storefront/inventory/sku/YOUR_PRODUCT_SKU/transactions?locationId=YOUR_SAVED_LOCATION_NAME"

Keep the intake ID returned by creation. The initial receive response may not contain a top-level sk; its data includes the status and bill link. Read the intake again using your retained ID. Verify the movement reference points to its intake number and that a repeated receive doesn't add stock again.
Read GET /repository/get/bm_bill/BILL_SK. The approved bill is owed, not paid. Journal posting is asynchronous after receipt. Query journal_entry for data.referenceType: "bill" and data.referenceId: BILL_SK; inspect posted status, balanced totalDebits/totalCredits and the inventory/payable lines. An empty booksPending on the bill plus its posted journal establishes the observed accounting result. If a marker remains, inspect the pending-books status instead of pretending the receipt response proves posting finished.
Existing reorder bills can be linked/folded into an intake. A zero-cost intake can add stock without a new supplier bill. This course's positive-cost example had no prior reorder and produced one bill. Inspect the response's bill/booksNote/booksSettled fields; do not hard-code that every receipt must create a new bill.

Recovery exercises
The teaching supplier changes behavior for these shipment suffixes:
| Suffix | Expected behavior |
|---|---|
-FAIL | 503 supplier unavailable; no intake write |
-SLOW | 3-second delay; set SUPPLIER_TIMEOUT_MS=250 to exercise timeout |
-UNKNOWN | Walnut code becomes NW-ASH-30; fails until you create/map the corresponding product |
-BAD | Negative quantity; rejected before write |
-CURRENCY | EUR response; refused when receiving currency USD |
An incorrect supplier key returns 401. Correct it before retrying; don't endlessly retry authentication failure. Missing mapping: add the correct product and supplierSku, then retry the same shipment. Ambiguous mapping: change the incorrect product's supplierSku; don't select the first returned record. Validate received source quantities, SKU and currency before a business write.

A local lock named for organization+reference refuses another worker. If a process dies, its lock intentionally remains. Inspect the exact AppEngine intake reference and reconcile the job before an operator removes a stale lock.
Before intake creation the adapter writes a dispatching marker. A known 4xx rejection clears that marker. For an uncertain network/5xx outcome it re-reads the exact reference: a saved record returns reconciled; no saved record leaves uncertain. A subsequent run refuses another create while that marker remains. A durable shared queue/lock store is required across multiple machines. Read-before-create alone cannot prevent concurrent duplicates.
The recorded lost-response exercise used a local proxy: AppEngine really saved the intake, then the proxy deliberately replaced the success response with 503. The adapter recovered that same ID. Another proxy test never forwarded the write; the adapter retained an uncertain marker and refused a blind retry. These are controlled transport tests, not production outage claims.



Cancel and correct
A pending intake can be cancelled with a required reason:
{"reason":"Supplier notice had the wrong quantities; corrected intake follows."}Send it to PUT /storefront/inventory/intakes/INTAKE_SK/cancel. Keep the cancelled record. Create the corrected pending intake with the same supplier reference and the quantities actually expected/received, then receive the corrected record when checked. The adapter excludes cancelled records during active-reference reconciliation.
Receiving a cancelled intake fails; cancelling a received intake fails. Once goods have been received, use the inventory adjustment/return business process rather than modifying stock history or received intake fields through Repository.


Inspect individual requests in any client
client.mjs, client.py and client.sh take method, relative path and optional JSON filename. They print the status and full JSON, not jq output. Import supplier.postman.json in Postman and set its base, orgid and token privately. Its environment placeholders are empty; it contains no credentials. Record IDs in observed responses are examples, not portable resource IDs.
requests/ contains real request bodies; observed/ contains sanitized course evidence. Postman is an equivalent collection; its GUI was not separately executed in this recording.
Deploy your integration
Replace supplier transport/authentication with the provider's documented contract. Validate webhook authenticity before enqueueing; polling should persist its cursor. Both feed the same serialized shipment worker. Advance job status/cursor only after saving and readback. Refresh expired AppEngine sessions through the supported authentication flow. Use shared durable job coordination across hosts; bound retries and retain diagnostics without credentials. The adapter deliberately does not claim to implement every supplier's OAuth, webhook signature, pagination or delta protocol.
Check retry coordination before deployment
The adapter reads every matching intake page before accepting one active exact reference. It refuses two active matches even when they are on different pages. Both the directory lock and persisted outcome marker use the same SHA-256 key of the JSON pair [organizationId, reference]; sharing a state directory across organizations therefore does not merge their shipment outcomes.
Run node adapter-regression.mjs (Node 24 for the TypeScript case) and python3 adapter-regression.py. These no-network tests exercise a duplicate split across two pages and an uncertain shipment in one organization while the same supplier identifier is imported independently in another. They supplement the live AppEngine outcome proofs; they do not simulate a commercial supplier success.
When upgrading an earlier local copy of this example, reconcile any existing locks and uncertain markers before migrating state. Do not discard an old uncertainty marker merely because a filename convention changed.

Observed course outcomes
The first synthetic shipment created intake INT-P3QZ0EAQ, total 2272, then receipt added 40 oak and 12 walnut. Bill B-INT-P3QZ0EAQ remained approved/unpaid; its journal separately reached posted and balanced 2272. A subsequent UI restock INT-GECK8W0T added 20 oak at 41.50 and 6 walnut at 70, producing 60 oak at 39.50 and 18 walnut at 64. Its 1250 bill had one posted journal; repeating its receipt did not add stock or another bill.
The cancellation exercise retained INT-QBATNHJ0 as cancelled with its reason. Its corrected replacement used 18 oak and 6 walnut and was separately received; the original remained cancelled. These are successive states, so a learner reproducing the whole sequence will have a later stock total than the 60 units shown at the restock checkpoint. The initial response-only inventory receipt and later Studio receipt are distinct deliberate demonstrations.
A repaired missing mapping, separate Python/TypeScript run, and lost-response reconciliation created additional pending training intakes. They were not silently received or claimed to increase stock. Example IDs are evidence only; use your own returned IDs.
Where to continue
- AppEngine Introduction: account, organization and API address.
- Database & Repository APIs: staff sign-in, built-in field overrides and exact record operations.
- Authentication: identity, organization and challenge branches.
- Inventory: inventory controls, adjustments and related operations.
- Integrations and Webhooks: provider-specific configuration and callbacks.
- Accounting APIs: bills and accounting operations beyond this integration.
These references identify what each lesson supplies; a written reference is not presented as an already produced provider video.
Verification and limitations
JavaScript, TypeScript and Python adapters were run against real isolated AppEngine records. TypeScript compilation and JavaScript/Python/curl request clients passed. Postman is an equivalent collection, not a separately filmed GUI walkthrough. Supplier failure controls and lost-response proxy are labelled teaching mechanisms. No commercial supplier integration, real shipment, bill payment, production webhook validation or distributed-lock deployment is claimed.
Current implementation source reviewed: AppEngine inventory-intake controller/service/rules, inventory movement service, pending-books and posting service, Repository controller, and the Studio Inventory Intakes view. Current source queues journal posting after receipt; this course checks that separate result. Creating an intake does not enforce a unique external reference; serialized jobs and uncertainty reconciliation belong in the adapter.