Files
ScadaBridge/docs/api-specs/Delmia-Recipe-Download-API.md
T

12 KiB

DelmiaRecipeDownload — ScadaBridge Inbound API spec (as deployed)

Captured: 2026-09-02 from the live prod management API on wonder-app-vd03 (method id 4, API keys, the CvdReactor template's ProcessRecipeDownload script, the DelmiaReceiver template, and the inbound audit log). QA (vq03/vq04) was NOT read — its management API requires real LDAP credentials (prod runs with DisableLogin); re-capture QA with scadabridge api-method get --id … once credentials are to hand. The definition is config-as-data in the central ConfigDb (not in git), so this file is the durable record; the legacy predecessor is documented in ../former-api-specs/dnc/Delmia-Integration-API.md (Surface B), and the client that calls it is the WWNotifier.exe drop-in (DelmiaNotifier).

1. Endpoint

Item Value
Method / path POST /api/DelmiaRecipeDownload
Auth X-API-Key: sbk_<keyId>_<secret> or Authorization: Bearer sbk_<keyId>_<secret> (Authorization wins if both)
Content type application/json, flat PascalCase object
Server timeout 45 s (TimeoutSeconds on the method); the site-side handshake wait inside it is 25 s
Prod base URL http://wonder-app-vd03.zmr.zimmer.com:8085 (single central node)
QA base URLs http://10.220.158.2:8085 (vq03), http://10.220.158.3:8085 (vq04) — list both in the notifier's BaseUrls; a standby central relays to the active peer

Unauthenticated / bad-key calls return 401 {"error":"Invalid or missing API key"} (audit kind InboundAuthFailure). General Inbound API status semantics (413 body cap, 503 STANDBY_NODE / ACTIVE_NODE_UNREACHABLE, unknown method) are in Component-InboundAPI.md.

2. Request

Deployed ParameterDefinitions (JSON Schema, verbatim — note required is empty; the script itself rejects a missing/blank MachineCode):

{"type":"object","properties":{
  "MachineCode":{"type":"string"},
  "DownloadPath":{"type":"string"},
  "WorkOrderNumber":{"type":"string"},
  "PartNumber":{"type":"string"},
  "JobStepNumber":{"type":"string"},
  "Username":{"type":"string"}},
 "required":[]}
Field Notifier flag Meaning Where it lands on the site
MachineCode -m Routing key — must equal an instance UniqueName exactly (trimmed). Prod today: Z28061, Z28062, Z28061Sim. Route.To(MachineCode)
DownloadPath -d Path where the recipe/NC file was placed DelmiaReceiver.DownloadPath
WorkOrderNumber -w Work order DelmiaReceiver.WorkOrderNumber
PartNumber -p Part / item number DelmiaReceiver.PartNumber
JobStepNumber -s Job step / sequence op DelmiaReceiver.JobStepNumber
Username -u Operator (audit only) DelmiaReceiver.Username

Example (a real call taken from the prod audit log):

curl -s -X POST http://wonder-app-vd03.zmr.zimmer.com:8085/api/DelmiaRecipeDownload \
  -H "X-API-Key: $SCADABRIDGE_API_KEY" -H "Content-Type: application/json" \
  -d '{"MachineCode":"Z28061Sim","DownloadPath":"C:\\recipes\\test.nc","WorkOrderNumber":"WO-TEST-001","PartNumber":"PN-TEST","JobStepNumber":"0100","Username":"delmia-sim-test"}'

3. Response

Deployed ReturnDefinition: {"type":"object","properties":{"Result":{"type":"boolean"},"ResultText":{"type":"string"}},"required":["Result","ResultText"]}

HTTP 200 is returned for every business outcome — success is Result == true, never the status code. ResultText values the deployed scripts can produce:

Result ResultText Produced by
true echo of DelmiaReceiver.RecipeProcessResultText (often "") Galaxy object acknowledged the download
false DelmiaReceiver.RecipeProcessResultText Galaxy object rejected the download (RecipeProcessResult false)
false MachineCode is required central script, blank MachineCode
false Receiver ready flag not set to true site script, ReadyFlag not true (or unreadable — see §7)
false Timeout waiting for recipe to be processed site script, RecipeProcessedFlag not true within 25 s
false RecipeDownload failed: <exception> either script — e.g. unknown instance, site unreachable, Script 'ProcessRecipeDownload' on instance 'X' failed: …

4. Processing flow

Delmia ─► WWNotifier.exe ─► POST /api/DelmiaRecipeDownload (central, active node)
                              │ inbound method script (id 4): validate MachineCode
                              ▼
                     Route.To(MachineCode).Call("ProcessRecipeDownload", {5 fields})
                              │ gRPC central→site, lands on the instance actor
                              ▼
      CvdReactor.ProcessRecipeDownload (site Call script) on the composed DelmiaReceiver child:
        1. read  ReadyFlag            → must be true, else "Receiver ready flag not set to true"
        2. write RecipeProcessedFlag  = false
        3. WriteBatchAndWaitAsync({DownloadPath, WorkOrderNumber, PartNumber, JobStepNumber, Username},
                                  trigger RecipeDownloadFlag = true,
                                  wait RecipeProcessedFlag == true, timeout 25 s)
        4. read  RecipeProcessResult / RecipeProcessResultText → {Result, ResultText}

Both scripts as deployed on prod (verbatim):

Central inbound method script (id 4, timeout 45 s)
try {
  System.Func<string, object> P = k => Parameters.ContainsKey(k) ? Parameters[k] : null;
  var code = (P("MachineCode") as string ?? "").Trim();
  if (string.IsNullOrEmpty(code)) return new { Result = false, ResultText = "MachineCode is required" };
  var suf = code.Substring(code.Length - 1);

  return await Route.To(code).Call("ProcessRecipeDownload", new {
      DownloadPath    = P("DownloadPath"),
      WorkOrderNumber = P("WorkOrderNumber"),
      PartNumber      = P("PartNumber"),
      JobStepNumber   = P("JobStepNumber"),
      Username        = P("Username"),
  });
} catch (Exception ex) {
  return new { Result = false, ResultText = "RecipeDownload failed: " + ex.Message };
}
Site script CvdReactor.ProcessRecipeDownload (template 1, script id 4, trigger call)
// Generic DELMIA recipe-download — performed DIRECTLY on the composed DelmiaReceiver child
// (mirrors how MesMoveIn writes the MESReceiver child) instead of delegating via CallScript.
// No side logic; the receiver's own DelmiaReceiver.Download method holds the same handshake.
var recv = Children["DelmiaRecv"];
System.Func<object,bool> IsTrue = v => { var s=v?.ToString(); return (v is bool b && b)||s=="1"||string.Equals(s,"true",System.StringComparison.OrdinalIgnoreCase); };
System.Func<string,object?> P = k => Parameters.ContainsKey(k) ? Parameters[k] : null;
if (!IsTrue(recv.Attributes["ReadyFlag"]))
    return new { Result=false, ResultText="Receiver ready flag not set to true" };
try {
    recv.Attributes["RecipeProcessedFlag"] = false;
    var data = new System.Collections.Generic.Dictionary<string, object?> {
        { "DownloadPath", P("DownloadPath") },
        { "WorkOrderNumber", P("WorkOrderNumber") },
        { "PartNumber", P("PartNumber") },
        { "JobStepNumber", P("JobStepNumber") },
        { "Username", P("Username") },
    };
    if (!await recv.Attributes.WriteBatchAndWaitAsync(data, "RecipeDownloadFlag", true, "RecipeProcessedFlag", true, System.TimeSpan.FromSeconds(25)))
        return new { Result=false, ResultText="Timeout waiting for recipe to be processed" };
    return new {
        Result = IsTrue(recv.Attributes["RecipeProcessResult"]),
        ResultText = recv.Attributes["RecipeProcessResultText"]?.ToString() ?? ""
    };
} catch (Exception ex) {
    return new { Result=false, ResultText="RecipeDownload failed: " + ex.Message };
}

5. Galaxy-side contract — the DelmiaReceiver template (id 12; composed clone id 26)

All ten attributes are data-sourced over the site MxGateway connection, bound per instance to the reactor-level Galaxy object (prod: DelmiaReceiver_038 on Z28061, _039 on Z28062, _037 on Z28061Sim; Left_00x.DelmiaReceiver / Right_00x.DelmiaReceiver back the unused side compositions).

Attribute Type Direction Role in the handshake
ReadyFlag Boolean Galaxy → SB Precondition; must be true or the call is refused
RecipeDownloadFlag Boolean SB → Galaxy Trigger — set true after the five data fields are written
RecipeProcessedFlag Boolean both SB clears to false first; Galaxy sets true when done (waited ≤ 25 s)
RecipeProcessResult Boolean Galaxy → SB Outcome → Result
RecipeProcessResultText String Galaxy → SB Outcome text → ResultText
DownloadPath, WorkOrderNumber, PartNumber, JobStepNumber, Username String SB → Galaxy Payload, written as one batch before the trigger

6. API keys scoped to this method (prod, names only — secrets are shown once at creation)

mes, mes api key, delmia-mes-receivers, TestKey, JOE_TEST_KEY — all enabled, all also scoped to the MES methods (MesMoveIn, MesMoveOut, MesSampleData, SimpleAlarmStatusRequest, most also IpsenMESMoveIn). delmia-mes-receivers is the one whose name says it is the Delmia caller's key; the audit actor column shows which key served each call.

7. Observed behaviour and open issues (from the prod audit log, 2026-06-17 → 2026-06-26)

Nine calls total, all test traffic (actors delmia-mes-receivers, delmia-sim-test), none since 2026-06-26 — no production Delmia traffic has hit this method yet. Every 200 answered Result=false: mostly Receiver ready flag not set to true, one Script 'ProcessRecipeDownload' … The given key 'side' was not present from an earlier script revision, and one MachineCode must end with A (Left) or B (Right) from a retired left/right variant (the current script routes on the whole MachineCode; the leftover suf variable is unused).

  1. Likely broken since the 2026-06-27 rename — verify before go-live. The site script still addresses the child as Children["DelmiaRecv"], but the CvdReactor composition was renamed DelmiaRecv → DelmiaReceiver that day (../deployment/deployment-records/2026-06-27-wonder-app-vd03.md §2b), so the flattened paths are DelmiaReceiver.*. ChildrenAccessor just concatenates the name into a path and GetAttribute on an unknown path logs a warning and yields null, so IsTrue(null) is false and every call will answer Receiver ready flag not set to true regardless of the real flag. No call has been made since the rename, so this is inferred from the code, not observed. Fix = change that one line to Children["DelmiaReceiver"] on the template (scadabridge template script update) and redeploy the three CvdReactor instances; check QA's copy of the script for the same string.
  2. Not idempotent. The design requires idempotency on (MachineCode, DownloadPath, WorkOrderNumber) because the notifier retries on timeout/connect failure (at-least-once). The deployed script re-runs the full handshake on a duplicate, so a retry after a slow first attempt will trigger a second download on the Galaxy object.
  3. required is empty in the parameter schema; only MachineCode is enforced, in script. Missing payload fields are written to the receiver as null/empty.
  4. QA definitions unread (credentials) — assumed identical (same build lineage), not verified.