Starship Horizons
Log In
☰
Knowledge Base›Web API Writes

Web API Writes

Some kinds of content can be created, replaced and deleted through the API. This is what the Hydra Studio editors use for those kinds, and it is open to your own tools on the same terms. Every write needs the server's token; Web API explains where it comes from. What each kind contains is on Web API Content.

Before you write anything

  • Send the token as Authorization: Bearer <token>, and the record as JSON with Content-Type: application/json.
  • Read first. A write replaces the whole record. Get the record, change it, and send all of it back.
  • Write into your own module. Every record names the module it belongs to, and that is where it is saved. A packaged (.hyd) module refuses every write.
  • Keep a copy. Writes go straight to the files on disk and there is no undo. Keep your module in version control.

What can be written

EndpointPOSTPUTDELETEKey in the record
commoditiesYesYes/{name}, or add ?moduleID=Name
componentsYesYes/{id}ID
component-partsYesYes/{id} or /{module}/{id}ID
companiesYesYes/{id}ID
factionsYesYes/{id}ID
ordnanceYesYes/{name}Name
modelsYesYes/{name}Name
pediaYesYes/{id}ID
dialogsYesYes/{key}IDL: the dialog id, plus its language when it has one
encountersYesYes/{name}Name
names-callsign and the other name listsYesYes/{name}Name
sound-banksNoYes/{module}/{id}ID
effectsNoYes/{module}/{id}ID
input-actionsNoYes/{module}/{id}ID
input-rolesNoYes/{module}/{id}ID
usersNoYes, partial/{account}-

The three requests

POST /api/{name} creates a record. It fails with 409 if that module already has one under the same key.

PUT /api/{name}/{id} creates the record if it is new (201) or replaces it (200). The key in the body has to match the {id} in the path; leave it out and the path's is used. Dialogs are the exception: their IDL must be in the body and must match.

DELETE /api/{name}/{id} removes a record. Which module's copy goes:

  • Usually the copy the game is using, the one in the module that wins the merge.
  • Commodities take ?moduleID= to name the module instead.
  • Component parts, sound banks, effects, input actions and input roles use /api/{name}/{module}/{id}, because the same id can be written in several modules. For sound banks, effects and input actions and roles, name the module: the shorter form looks only in the server's default module. A bank that another module defines answers not-owned.
  • An encyclopedia entry is removed from the first module found to have it.

When the deleted copy was overriding another module's, the other one shows through again.

Every record sent with POST or PUT must carry ModuleID, naming a module the server has loaded. Users are the one exception: the player registry belongs to the server, not to a module.

Answers

A write always answers with an envelope. On success, data is the record as stored, or for a delete, what was removed:

{ "ok": true, "data": { "Name": "Coolant", "ModuleID": "MyModule", ... } }
{ "ok": true, "data": { "id": "Coolant", "moduleID": "MyModule", "deleted": true } }
{ "ok": false, "error": "id-name-mismatch" }
StatusError codes and meaning
200Replaced, or deleted.
201Created.
400The request is wrong: empty-body, bad-json, id-required, name-required, idl-required, moduleID-required, id-mismatch, id-name-mismatch, idl-mismatch, extends-self (an input role that extends itself), no-layers (an effect with nothing in it), not-owned. A record that cannot be read at all, such as one with a value that is not in its list, answers with the reason in place of a code. Nothing is written.
401unauthorized: the token is missing or wrong.
403module-sealed: the module is a package and cannot be changed. See Packaging Modules.
404unknown-endpoint, unknown-module, not-found, unknown-name-type, unknown-user.
405method-not-allowed: that endpoint does not take that request, such as a POST to sound-banks.
409already-exists: a POST for a key the module already has.
500save-failed (the file could not be written, and the change was put back) or internal-error. debug.log says why.

The rules

A write is the whole record

PUT does not merge. Any field you leave out takes its default, and /api/describe-schema/{kind} shows what those defaults are. A tool that reads three fields, changes one and sends back three has just reset everything else.

Two writes on this API are deliberately partial:

  • PUT /api/users/{account} changes only the fields the body contains: CallSign, FirstName, LastName, Rank, Faction, Race, Notes, Blocked, Trusted, ETS, CharacterID, Assignments (which replaces the whole set of remembered seats) and Devices, a list of { DeviceID, Blocked, VisualStyle, VocalAssist, Language }. A player who is connected sees the change at once. DELETE forgets the player and their devices.
  • PUT /api/input-roles/{id} on an existing role keeps the role's default controller layouts when the body has no Controllers. Send Controllers to replace them.

Keys are names, and nothing renames

Records are found by their key, and a write cannot change it: a body whose key differs from the path is refused. To rename, create the record under the new key, point everything that used the old one at it, and then delete the old one. Nothing follows the change for you — a mission still names the old vessel class, a vessel class still names the old component.

Components are the one place where the key and the name you see differ: a component is keyed by ID, and its Name is display text that a PUT can change freely. A new component sent without an ID takes its Name as one.

A write lands in one module

The record is saved into the module its ModuleID names, and the server rebuilds its merged view straight away, so the next read returns the change. If a module loaded later defines the same key, that one still wins: writing the earlier module's copy changes its file and nothing in play.

Many kinds keep all of a module's records in one file — commodities, companies, ordnance, models, encounters, the encyclopedia index, component parts, sound banks, effects, the name lists, input actions and input roles. A write to one record rewrites that whole file from what the server holds, so comments and hand formatting in it are not kept. Components, factions and dialogs are one file per record.

What the API does not write

Missions, vessel classes, maps, campaigns, modules, NPC roles, task definitions, bots, screens and devices have no write on this API. Hydra Studio's editors save them, and /api/endpoints names the command each uses (see Server Commands). The same rules hold — a save is the whole record, the name is the key, nothing renames — with these additions:

  • Missions. A save that leaves out Zones, Bots or AllowedBots keeps the ones the mission already has. Sending an empty list clears them. The mission's script is saved inside the mission file; see Mission Scripting.
  • Maps. A save is applied to the map as loaded. One that leaves out Zones keeps them, and a save cannot turn a map into a tile map or back: change that in the map file. A map with any field that cannot be read is refused whole.
  • Roles. A role saved without a Schedule keeps its timetable. A role goes into the module its ModuleID names; if a module loaded later overrides the same role, the override keeps winning in play. Every role that inherits from a changed one picks the change up at once.
  • Task definitions follow the same module rule as roles. The editor locks a plan's name once it exists.
  • Bots are keyed by id, and a packaged module refuses them.

Examples

Create a commodity in your own module, from a bash shell (Git Bash, macOS, Linux):

curl -X POST http://localhost:1864/api/commodities \
     -H "Authorization: Bearer 3f9c0d..." \
     -H "Content-Type: application/json" \
     -d '{"Name":"Coolant","ModuleID":"MyModule","Description":"Reactor coolant, in drums.","UnitOfMeasure":"L","Weight":40}'

{"ok":true,"data":{"Name":"Coolant","ModuleID":"MyModule", ... }}

Change one field of a record, in PowerShell, by reading it, editing it and sending all of it back:

$token = "3f9c0d..."
$url   = "http://localhost:1864/api/factions/Corsairs"

$f = Invoke-RestMethod $url
$f.Description = "Raiders out of the outer belt."

Invoke-RestMethod -Method Put -Uri $url `
    -Headers @{ Authorization = "Bearer $token" } `
    -ContentType "application/json" `
    -Body ($f | ConvertTo-Json -Depth 32)

Then check your work: GET /api/validate/MyModule runs the same checks as the editor's Problems panel over your module.

Last updated 19 October 2026