Driving the Web Service over HTTP
The HiNC web service exposes everything its browser client does as a REST API, so a script or an agent can build, save and run a project with no browser open. This page is what such a caller needs around its requests: where the contract is and where it is wrong, how to sign in and read a reply, how files and projects are addressed, the key-based pattern most setup routes share, and how to replay a mission without disturbing anyone else. The order in which a project is assembled is Project Construction.
Contract and Sign-In
The service publishes its contract as two OpenAPI documents, both served in every environment:
- The Swagger document, opened from the Swagger UI at
/swaggerand served at/swagger/<contract version>/swagger.json— currently/swagger/v3.1/swagger.json. The UI always loads the current one, so a guessed/swagger/v1/swagger.jsonis a 404, not a missing contract. The UI and its document answer ahead of the sign-in stage and are readable without a session. - The framework document at
/openapi/v1.json. It is an ordinary routed endpoint, so while the login gate is on it needs the session cookie like any other route.
Routes and query parameters agree between the two. Request bodies do not. The framework document names every schema by its simple type name, so request types that share a name in different controllers collapse into one schema, and several routes advertise another route's body. Take body shapes from the Swagger document, which names schemas by full type name. Where only the framework document is at hand, these are the routes it gets wrong:
| Route | Shown as | Actually reads |
|---|---|---|
POST /api/mech/machine-tool/load |
{ xmlText, loadResources } |
{ rootName, relFile }, with rootName either ProjectDirectory or ResourceDir |
POST /api/Workpiece/workpiece-material/load |
{ xmlText, loadResources } |
{ rootName, relFile } |
POST /api/Workpiece/cutting-parameter/load |
{ xmlText, loadResources } |
{ rootName, relFile } |
POST /api/mech/spindle-capability/load |
{ xmlText, loadResources } |
{ xmlText, baseDirectory, relFile }; loading by path is POST /api/mech/spindle-capability/load-file with { rootName, relFile } |
POST /api/file-explorer/rename |
{ name } |
{ rootName, relativePath, newRelativePath } |
JSON property names are matched case-insensitively, so RootName and rootName are the same field,
and enumerations travel as their names. Values are not relaxed the same way: root names and geometry
or transformer kind names are compared exactly as written on this page.
Check the login gate before anything else. The shipped configuration leaves it off, so an
installed copy answers every route with no session, on its own computer only: its address is
localhost, and while the gate is off a request addressed to any host name but localhost,
127.0.0.1 or [::1] is answered 400 Bad Request unless the service's AllowedHosts lists that
name, so widening the address alone does not let a script on another computer in. A deployment can
turn the gate on, which lifts that host-name restriction unless it sets AllowedHosts, and while the
gate is on every route outside /api/auth answers 401 with an empty body until the caller has
signed in — which reads exactly like a service that is not running. GET /api/auth/status is always
anonymous and reports whether the gate is enabled, whether this caller is signed in, and the service
version. Sign in with POST /api/auth/login and the JSON body { username, password }. A good pair
answers { authenticated: true, … } and sets a cookie every later request must carry, so use an HTTP
client with a cookie jar; a wrong pair answers 401 with the code InvalidCredentials. With the gate
off, the login answers success without reading the body, so a script may sign in unconditionally. A
script in a browser page works only from a page the service served itself: the service sends no CORS
headers, so no other page may read its answers, and while the gate is off it answers 403 to another
page's requests — one on another port of the same computer included. A script outside a browser
sends neither Origin nor Sec-Fetch-Site and is not affected. Some routes answer with data but
are POSTs, because they also register the object they return for later editing, write files as they
serialize, or open a path you name: the Tool House holder and beam reads (CylindroidHolder/Get,
FreeformHolder/Get, Cutter/{id}/inner-beam, Cutter/{id}/upper-beam), ObjectManagement/GetXml,
mech/machine-tool/xml, NcProgram/file-lines and StlFile/GetFileInfo. A GET to one of them is
answered with the application's HTML page, not an error. A few GETs still fill in a controller
parameter table's missing default as they read it, which a later project save writes out.
Then read GET /api/Project/status once. It reports whether a project is open, its path, and the
absolute admin and project folders on the server. Confirm the admin folder is the tree meant to be
written into before writing anything: a service started from a different configuration comes up just
as healthy, pointed at a different tree.
Reading a Reply
Routes take their input in one of three shapes, and the Swagger document says which: a JSON object
body such as { rootName, relFile }; query parameters, which is how most key-based edit routes take
both their key and their values — a route that takes a list, such as
POST /api/Cylindroid/UpdateAllPairs, takes the key as a query parameter and the list as a JSON body
(see Editing Objects by Key); or a bare JSON
value as the whole body — PUT /api/Workpiece/init-resolution takes the number itself, 0.25, not an
object.
An HTTP 200 does not by itself mean a request did what it asked. The service keeps the convention its
browser client relies on: a refusal that is not a malformed request comes back as 200 with
success: false and a message, and some partial successes come back as success: true with a
warning. For example:
POST /api/mech/machine-tool/loadnaming a file that does not exist, or called with no project open, answers 200 withsuccess: false; so do the workpiece-material and cutting-parameter loaders for a missing file or one that does not deserialize;POST /api/StlFile/UpdateSourcewhose file cannot be read answers 200 withsuccess: trueand awarning— the reference is stored anyway; a new StlFile then has no mesh, while one that already held a mesh keeps the old one, whichGetInfogoes on reporting and the next save writes under the new name, so comparetriangleCountwith the file, not onlyloaded;POST /api/Mission/list-command/entrieswith a command kind the catalog does not know answers 200 withsuccess: false.
Malformed requests answer 400 — an unknown root name, a path that leaves its root, an empty required
field. An object-store key that does not exist answers 404, and a key holding the wrong kind of object
answers 409. Most routes that create an object in the store answer with its key as a bare string —
plain text, unless the request's Accept header asks for JSON, which yields a quoted JSON string; a
few, such as POST /api/Cutter/{id}/upper-beam/create, answer a JSON object carrying the key.
A reliable caller therefore checks the status code, then success, then warning, and follows each
write with the matching read before building on it: GET /api/mech/machine-tool after a chain load,
GET /api/StlFile/GetInfo (loaded, triangleCount) after pointing an STL at a file,
GET /api/Workpiece/Status once the workpiece is set up.
One Service, One Project
A service instance holds exactly one open project and one object store, shared by every browser and
every script connected to it. Signing in decides who may connect, not whose project it is.
POST /api/Project/new or POST /api/Project/load replaces the project everyone on that instance is
working on, and the run controls act on whatever run is current: POST /api/Execution/stop takes no
run identifier and stops the run in progress, whoever started it.
Before driving an instance other people may be using:
- Read
GET /api/Project/statusandGET /api/Execution/status. Do not take over while another person's project is open or a run isRunningorPaused; a dedicated instance is the safe choice for a scripted build. - Give every object-store key a prefix no other caller uses (see Editing Objects by Key).
- Identify a run by the
runIdthatPOST /api/Execution/startreturns, and never stop a run from a timeout (see Running a Replay).
Files and Named Roots
Every file path the API takes is relative to one of three named roots, passed by name:
| Root name | Resolves to |
|---|---|
AdminDirectory |
the service's working area; the project paths given to /api/Project/* are relative to it |
ProjectDirectory |
the folder holding the open project file — unavailable, and refused with 400, while no project is open |
ResourceDir |
the shipped library, the Resource folder under the admin area |
GET /api/file-explorer/roots lists the roots available at the moment. A path that leaves its root
is refused with 400.
Files reach the server through the routes under /api/file-explorer:
| Route | Arguments |
|---|---|
POST upload |
query rootName and relativePath; the file as the multipart form field file. Missing parent folders are created and an existing file is overwritten. |
POST write-text |
body { rootName, relativePath, content }; written as UTF-8 without a byte-order mark, parent folders created |
POST create-directory |
body { rootName, relativePath }; a no-op when the folder exists |
GET list, GET download, DELETE delete |
query rootName and relativePath; delete removes a folder recursively |
There is no cross-root copy: copy duplicates beside the source and rename refuses to move between
roots. Because ProjectDirectory exists only once a project is open, inputs staged before the
project is created go to AdminDirectory, under the folder the project file will sit in —
<project folder>/NC/…, <project folder>/Geom/… — and are addressed through ProjectDirectory
afterwards.
What a loader does with a file depends on where it came from:
- Pre-prepared resources — machine-tool chain, spindle capability (
load-file), workpiece material, cutting parameters, cutter material — are loaded by{ rootName, relFile }. A file already inside the project is referenced where it is. A file picked underResourceDiris written into the project at the same sub-path, for exampleWorkpieceMaterial/<name>, and referenced there, so the saved project never points into the shared library; a file picked elsewhere underAdminDirectoryis written into the project root under its own name. Uploading the file into the project first and loading it fromProjectDirectorycomes to the same thing, and it is the form to use whenever the copy is not there: after a pick from the library, list the project's folder (GET /api/file-explorer/list?rootName=ProjectDirectory&relativePath=WorkpieceMaterial, and so on) and check the file landed — an instance whose loaders do not copy stores a reference that resolves to nothing, and the next save or close fails withCould not find file. The machine-tool loader accepts onlyProjectDirectoryandResourceDir, and a chain picked from the library comes with its side files. - An STL set with
POST /api/StlFile/UpdateSourceand{ stlFileKey, rootName, relFile }is recorded project-relative; one picked outside the project is recorded asGeom/<file name>, and its mesh is written there at the latest on the next save — but only when no file already sits at that path: an existingGeom/<file name>is kept and the reference then names it. Give meshes from different folders distinct names, or upload them into the project first. - NC programs are recorded as the plain path a mission command names. Nothing copies them: upload them.
Project Lifecycle
The project routes work on the one open project, addressed by a path relative to AdminDirectory:
| Route | Body | Effect |
|---|---|---|
GET /api/Project/status |
— | hasProject, projectPath, and the absolute admin and project folders |
POST /api/Project/new |
{ relativePath } |
creates the folder if needed, writes the new project file at once and opens it; refused with 400 when a file already exists at that path |
POST /api/Project/load |
{ relativePath } |
opens a project; the reply's messages lists load-time holes, such as a missing STL or a referenced side file that cannot be read (XmlSource-Read--Failed), none of which fails the load; a project file that itself cannot be read — missing, locked, malformed — is refused with 400, the message naming the file under its root, and no project is open afterwards (the open project is released before the file is read; the status hub reports it) |
POST /api/Project/reload |
— | re-reads the open project from its file, with the same messages; a file deleted, locked or damaged since is refused with 400 and no project is open afterwards: the in-memory project is released before the file is read |
POST /api/Project/save, close |
— | act on the open project |
POST /api/Project/saveas |
{ relativePath } |
writes to a new path, which becomes the open project; a file already there is overwritten without a prompt |
save, reload, close and saveas answer 400 when no project is open. The six project-file
operations take turns: one requested while another is running is refused with 409 rather than queued.
A new project is not empty. It carries a fixture in its default shape with an empty solid on its
geometry anchor, a workpiece with no geometry, an empty tool house and no machine chain, and a mission seeded
with eight root entries: Machining Resolution, Machining Motion Resolution, Collision Detection, Pause
on Failure, Physics, two Program File entries naming the placeholders NC/NC-File-Name-1.nc and
NC/NC-File-Name-2.nc, and a Post-Execution. The placeholder programs do not exist, and each one left
enabled raises the error RunNcFile--NotFound on every play and is skipped; the play goes on. An entry added through
POST /api/Mission/list-command/entries without an insertIndex lands at the end, after the seeded
Post-Execution. Edit the seeded entries in place, or delete them with
DELETE /api/Mission/list-command/entries/{path}, highest index first, because the entries after a
deleted one renumber; then read the tree back with GET /api/Mission/list-command/entries.
Rebuilding at the same path is close, delete, new: POST /api/Project/close, then
DELETE /api/file-explorer/delete?rootName=AdminDirectory&relativePath=<project path>, then
POST /api/Project/new. The other files in the folder — uploaded programs and resources — are left as
they are.
Finish every build with save, reload and a read-back. Reload re-reads the file, so the settings read after it are the ones the file actually holds; a value that does not survive the round trip was never saved.
Editing Objects by Key
Geometry, transformers, cylindroid profiles and similar setup values are edited through an object store rather than through one route per field. A caller works in three moves:
- Create or index an object and get its key. A create route builds an object, installs it where
it belongs, stores it under a key and returns that key. The key is passed as a query argument
(
geometryKeyon the workpiece's create routes,keyon the per-kindNewandNewWithValue, …); when it is left out, a generated one is used and returned. An index route publishes an object that already exists — the geometry a workpiece already holds, the transformer inside a transformation geometry — and returns its key. - Edit through the key. Per-kind routes take the key as a query parameter and the new values as
query parameters too —
POST /api/StaticTranslation/UpdateTrans?staticTranslationKey=…&x=…&y=…&z=…— or a list as a JSON body. - Read back through the key —
GET /api/StaticTranslation/Get?staticTranslationKey=…,GET /api/StlFile/GetInfo?stlFileKey=…, orGET /api/Index/GetType?key=…for any key.
A vector is never an object of its own. The translation of a static translation, the axis and
pivot of a rotation and the two corners of a box are values of the object that owns them, and are
written through that owner's route with the owner's key and the three axes as query parameters:
StaticTranslation/UpdateTrans, StaticRotation/UpdateAxis and UpdatePivot,
DynamicRotation/UpdateAxis and UpdatePivot, DynamicTranslation/UpdateAxis, and Box3d/Update
with both corners. The owner's Get reads the value back, and the two rotations and the dynamic
translation also answer NormalizeAxis. No route hands out a key to a vector.
Container routes derive the keys of what they create from the container's own key, so a chain of calls can be scripted without parsing replies:
| Call | Key returned |
|---|---|
POST /api/Workpiece/CreateRawGeometry?geometryKey=K&geometryType=TransformationGeom |
K |
POST /api/TransformationGeom/CreateGeom?transformationGeomKey=K&geometryType=StlFile |
K-Geom |
POST /api/TransformationGeom/CreateTransformer?transformationGeomKey=K&transformerType=StaticTranslation |
K-Transformer |
POST /api/Workpiece/SetRawGeometryTransform?geometryKey=K&transformerType=StaticTranslation |
K — the geometry already in the slot, wrapped in place; K-Geom and K-Transformer are kept in step, and transformerType=None unwraps it again |
POST /api/Workpiece/Initialize?sessionKey=S |
a JSON object naming S-Workpiece, S-RawGeom and S-IdealGeom; a geometry key is indexed only when its slot holds something (hasRawGeom, hasIdealGeom) |
POST /api/Fixture/Initialize?sessionKey=S |
a JSON object naming S-Fixture |
POST /api/mech/equipment-topology/{owner}/branch/{guid}/index-transformer |
equipment-topology.<owner>.branch.<guid>.transformer — the transformer on that branch of the fixture, workpiece or machine tool; a branch carrying none is first given a pass-through one |
Placing a mesh as the stock, translated into position, therefore runs as below, here with the
caller's own prefix job7-stock and a mesh already uploaded to Geom/stock.stl:
POST /api/Workpiece/CreateRawGeometry?geometryKey=job7-stock&geometryType=TransformationGeom
POST /api/TransformationGeom/CreateGeom?transformationGeomKey=job7-stock&geometryType=StlFile
POST /api/StlFile/UpdateSource { "stlFileKey": "job7-stock-Geom", "rootName": "ProjectDirectory", "relFile": "Geom/stock.stl" }
GET /api/StlFile/GetInfo?stlFileKey=job7-stock-Geom -> loaded: true, triangleCount > 0
POST /api/TransformationGeom/CreateTransformer?transformationGeomKey=job7-stock&transformerType=StaticTranslation
POST /api/StaticTranslation/UpdateTrans?staticTranslationKey=job7-stock-Transformer&x=-45&y=-30&z=-12
POST /api/Workpiece/UpdateRawGeometry?geometryKey=job7-stock (re-install, next section)
POST /api/Workpiece/ClearRawGeomCache
A create route replaces whatever the slot held; nothing carries over from the object it
displaced. Wrapping what is already there is a separate family, the owners' Set…Transform routes —
Workpiece/SetRawGeometryTransform, Workpiece/SetIdealGeometryTransform,
mech/equipment-topology/{owner}/anchor/{id}/set-geom-transform,
FreeformHolder/SetGeometryTransform, GeomCombination/SetItemTransformAt,
TransformationGeom/SetGeomTransform and Cutter/{id}/upper-beam/set-transform — each taking
transformerType, a transformer kind or None: a bare geometry is wrapped in a TransformationGeom
around that very object, a wrapped one gets a fresh transformer of that kind, and None hands the
inner geometry back to the slot; the route answers the slot's key. These are what the web client's
Transform picker posts, and the client never names TransformationGeom as a kind; the sequence
above stays valid for a script and builds the same wrapper.
Anchors and branches of the equipment
The transformers that place the fixture on the table and the workpiece on the fixture are not slots
with routes of their own. Each is the transformer of a branch in the topology of the part that
owns it, and the routes under /api/mech/equipment-topology address a part's anchors and branches
by guid, with the owner — machine-tool, fixture or workpiece — in the path:
GET /api/mech/equipment-topology/graphlists every anchor (guid,owner,roles) and every branch (guid,owner,fletchAnchorGuid,arrowAnchorGuid,transformerType,isAttachment). Find a branch by the role of the anchor it arrives at, never by its name: the workpiece branch whosearrowAnchorGuidis the workpiece anchor whoserolesincludeProgramZeroAnchoris the one that places program zero; the one arriving atFixtureMountplaces the workpiece on the fixture; the fixture branch arriving atTableMountplaces the fixture on the table. Skip the attachments (isAttachment: true, ownerequipment) — the equipment builds those itself from the roles.- Build a transformer under a key of the caller's own —
POST /api/StaticTranslation/NewWithValue?key=K&x=…&y=…&z=…— and install it withPOST /api/mech/equipment-topology/{owner}/branch/{guid}/update-transformer?transformerKey=K. The answer is the canonical key the transformer is re-indexed under,equipment-topology.<owner>.branch.<guid>.transformer, whichindex-transformeron the same branch also answers; editing through that key is the nested-edit case of the next section. - A role is bound with
POST /api/mech/equipment-topology/roles/{owner}/{role}and the body{ "guid": "…" }— an anchor guid forTableMount,ToolMount,WorkpieceMount,GeomAnchor,FixtureMountandProgramZeroAnchor, a branch guid forAxisX…AxisCandSpindle; an empty guid unbinds.GET roles/{owner}lists each role with its candidates and the checks that fail (Unbound,NotOwn,Duplicate,NotReachable,AxisTransformerMismatch,AxisOffChain,SpindleOffToolAxis).
Every edit under this controller rewires the equipment, and the attachment branches are given new
guids each time, so read the graph again before addressing one. The worktable attachment — from the
machine's table end to the fixture's TableMount, or to the workpiece's FixtureMount when there
is no fixture — is the one attachment with an editable transformer, addressed with the owner
equipment (…/equipment/branch/{guid}/index-transformer and update-transformer; its key is
equipment-topology.equipment.table-to-comp); any other attachment answers 409 AttachmentFixed.
The refusals carry a code: 400 UnknownOwner, 404 MechanismNotFound when the part is not
installed, 409 MechanismNotEditable for a cutter-location device, 409 RoleElementTaken when the
element already plays another role of the same part, 409 AxisTransformerMismatch when an axis is
given, or a bound axis branch is set to, a transformer of the wrong kind, and 409 SolidsNotAllowed
for a geometry route on the workpiece — its geometry is the raw and target slots above, not an
anchor solid.
The store is shared by every caller of the service and its keys are plain strings. Writing a key that
already exists silently repoints it, so every caller needs a key prefix no other caller uses. Keys stay
until removed with POST /api/Index/Remove?key=…; removing a key only forgets the handle and never
takes the object out of the project.
Making an Edit Reach the Run
A project holds its equipment twice: the setup that is saved, and a runtime copy that the runner, the physics, collision checking and the stock mesh are built from. The runtime copy is not patched field by field — apart from the environment values (spindle capability, coolant condition, ambient temperature), which the spindle-capability routes forward at once. It is rebuilt from the setup at two moments:
- at the start of a fresh session — the first play after a project is loaded, or after a Reset;
- when an install route marks the setup edited — at once when no session is open, otherwise at the
next play or Reset. The install routes are the ones that put an object into a slot or edit the
equipment's topology: creating or replacing a workpiece geometry, installing a transformer on a
branch with
update-transformer, binding a role, adding or deleting an anchor or a branch, loading a machine chain, a material or cutting parameters, aligning the workpiece to a work offset.
The status says when an install is waiting. GET /api/Execution/status answers hasSession —
something has played since the last Reset — and setupEquipmentDirty — an install route has marked
the setup edited and the runtime copy has not been rebuilt from it yet — beside resetCount, which
each Reset raises by one. While both flags are true the web client's Equipment page shows a banner
with a Reset now button. In host code the flag is
IsSetupEquipmentDirty, and
SetupEquipmentDirtyChanged is raised on each change.
An edit made through a child key marks nothing. StaticTranslation/UpdateTrans,
StlFile/UpdateSource, Box3d/Update, a cylindroid's pair updates and every other per-kind route
change the setup object in place. The change reaches the runtime copy at the next fresh session and not before: a play started on
a session that is still open — a second start with no Reset in between — runs against the old
placement or the old shape, and nothing in its messages says so. The setup canvas draws the
workpiece's raw and target solids from the runtime copy, so a nested edit to either geometry goes on
showing the old shape there; the anchors, the fixture and the machine it draws from the setup
itself.
After the last nested edit inside a slot or a branch, re-install the slot's own object — posting the same key again is enough — and clear its cached solid where it has one:
| Edited inside | Re-install | Then |
|---|---|---|
| Workpiece raw geometry | POST /api/Workpiece/UpdateRawGeometry?geometryKey=<slot key> |
POST /api/Workpiece/ClearRawGeomCache |
| Workpiece target geometry | POST /api/Workpiece/UpdateIdealGeometry?geometryKey=<slot key> |
POST /api/Workpiece/ClearIdealGeomCache |
| The transformer of a fixture, workpiece or machine-tool branch | POST /api/mech/equipment-topology/{owner}/branch/{guid}/update-transformer?transformerKey=<its key> |
— |
| The worktable attachment's transformer | POST /api/mech/equipment-topology/equipment/branch/{guid}/update-transformer?transformerKey=<its key> |
— |
This is what the browser client does after every nested edit, and it makes the edit effective whatever state the session is in. Resetting before every start is the second guard, and saving and reloading rebuilds the runtime copy as well — one more reason every build ends with save, reload and a read-back.
Running a Replay
A scripted replay is reset, start, then poll by run id:
GET /api/Execution/status. IfexecutionStatusisRunningorPaused, a run is live — do not reset, start or stop it; a reset stops a live run too.POST /api/Execution/reset. Start does not reset. After a finished run the session stays open, holding the machined workpiece, its steps and its messages, andPOST /api/Execution/startplays the mission again on top of it. Every Read On First Or Write record then sees steps already produced and writes instead of reading, so the stage-0 record overwrites the cached stock with the machined part — and every later run, reset or not, starts from stock that has already been cut until that file is deleted. Setup edits made through child keys are not picked up by such a play either. Reset before every start.POST /api/Execution/start, and read the body. An enabled Script command that does not compile refuses the start with HTTP 200,success: falseandcode: "script_compile_error", naming the script and the error. A start while a run is live — running or paused — answersalreadyRunning: trueand starts nothing. Keep therunIdit returns.- Poll
GET /api/Execution/statusuntil it reports thatrunIdwithexecutionStatuseitherFinishedorReady. After a Reset,Readyis how a run that was stopped, or that ended in an exception, reports itself, so a poller that waits forFinishedalone never returns from a failed run. Without a Reset in between, such a run inherits the previous run'sFinishedand passes for a completed one — one more reason to reset before every start. The exception behind aReadyis in the service log, whichGET /api/Project/logsreturns for the current day. A higherrunIdmeans another caller has started a run since, so this one is over. - Read what the run produced before anything resets the session: a Reset empties the step strip, the meshed workpiece and the session's message lists. The checks that tell a finished run from a passing one are in Replay Acceptance over the HTTP API.
If the poller gives up, let it give up. A timeout is not a reason to call POST /api/Execution/stop:
stop ends whatever run is current, which on a shared instance may not be this caller's, and a stopped
run skips everything below the point it reached — no Post-Execution output, no end-of-stage record.
Request Encoding
Send request bodies as UTF-8. The service decodes JSON as UTF-8, and a client that encodes the body in another code page loses every character outside that code page before the request leaves the machine — while the call still reports success. The damage shows only when the value is read back.
Windows PowerShell 5.1 is the usual case: Invoke-RestMethod with a string -Body and a content type
that carries no charset sends ISO-8859-1, so a group title written in Chinese is saved as ??.
Send bytes instead:
Invoke-RestMethod -Method Put -Uri $uri -WebSession $session `
-Body ([System.Text.Encoding]::UTF8.GetBytes($json)) `
-ContentType 'application/json; charset=utf-8'
or use an HTTP library that sends UTF-8 by default. The same holds for a program or any other text
sent through write-text: what the request body carried is what lands in the file.
See Also
- Getting Started with HiAPI — the in-process C# route to the same engine, with no service in between
- Project Construction — the build order these routes are used in, and which controller routes the run actually reads
- Replay Acceptance over the HTTP API — the evidence that tells a finished replay from a passing one
- Program and Hosting — the two OpenAPI documents, the request pipeline and where sign-in sits in it
- Login and Authentication — the gate's defaults, the cookie's lifetime and the three auth routes
- File Explorer Page — the complete file-route table
- Object Store (IndexService) — the store behind the key-based routes, and its lifetime
- Workpiece — the re-install-then-clear chain the browser client runs after every nested edit, and the workpiece's three roles
- Fixture — the fixture's roles, topology and solid, and the topology routes behind its panel
- Equipment Topology — the whole
api/mech/equipment-topologyroute table, the role checks and the error codes