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
| Endpoint | POST | PUT | DELETE | Key in the record |
commodities | Yes | Yes | /{name}, or add ?moduleID= | Name |
components | Yes | Yes | /{id} | ID |
component-parts | Yes | Yes | /{id} or /{module}/{id} | ID |
companies | Yes | Yes | /{id} | ID |
factions | Yes | Yes | /{id} | ID |
ordnance | Yes | Yes | /{name} | Name |
models | Yes | Yes | /{name} | Name |
pedia | Yes | Yes | /{id} | ID |
dialogs | Yes | Yes | /{key} | IDL: the dialog id, plus its language when it has one |
encounters | Yes | Yes | /{name} | Name |
names-callsign and the other name lists | Yes | Yes | /{name} | Name |
sound-banks | No | Yes | /{module}/{id} | ID |
effects | No | Yes | /{module}/{id} | ID |
input-actions | No | Yes | /{module}/{id} | ID |
input-roles | No | Yes | /{module}/{id} | ID |
users | No | Yes, 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" }
| Status | Error codes and meaning |
| 200 | Replaced, or deleted. |
| 201 | Created. |
| 400 | The 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. |
| 401 | unauthorized: the token is missing or wrong. |
| 403 | module-sealed: the module is a package and cannot be changed. See Packaging Modules. |
| 404 | unknown-endpoint, unknown-module, not-found, unknown-name-type, unknown-user. |
| 405 | method-not-allowed: that endpoint does not take that request, such as a POST to sound-banks. |
| 409 | already-exists: a POST for a key the module already has. |
| 500 | save-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.