The Nucleares API does not use conventional HTTP semantics. Almost every failure mode here is a silent one. Read this before writing a client.
[::1]:8785. Not 127.0.0.1. Full detail and the diagnostic command are in
scraping.md. It is listed again here because it is the single
most common way to lose an hour.
This is the central hazard. None of these are distinguishable from the status line, and four of the five look like success.
| Meaning | Example | How you can tell |
|---|---|---|
| Accepted and worked | CORE_SCRAM_BUTTON <- true |
Rods moved 93 to 100, CORE_STATE flipped, temperature trajectory reversed |
| Accepted, value silently discarded | EMERGENCY_BATTERIES_MODE <- MANUAL |
Reads back 1, not MANUAL. Invalid enum thrown away without complaint |
| Accepted, genuinely does nothing | CORE_END_EMERGENCY_STOP <- true |
Rods stayed 100, CORE_STATE stayed NOREACTIVO, no state moved anywhere |
| Accepted, target unresolvable | bogus valve identifier posted to VALVE_OPEN |
Nothing moves. The variable name was valid, the value named no real valve, and that is not rejected |
| Read of a nonexistent variable | GET ?Variable=NOPE |
HTTP 200 with body The readable variable 'NOPE' does not exist. |
A successful write returns HTTP 200 with the literal body null.
Two statuses do use honest codes:
| Meaning | Example | Signal |
|---|---|---|
| Writable but gated | any FUN_* member |
HTTP 412 |
| Write to an unknown variable name | POST ?Variable=NOPE |
HTTP 404, body The writable variable 'NOPE' does not exist. |
This is worth stating on its own because it is counterintuitive:
| Path | Unknown name | Status |
|---|---|---|
| Read | The readable variable 'X' does not exist. |
200 |
| Write | The writable variable 'X' does not exist. |
404 |
The write path is better behaved than the read path. A POST to a name that is not in the manifest fails loudly with a 404. A GET to a name that is not in the manifest succeeds with a 200 and hands you an English sentence where a value should be.
The practical consequence for client authors: you can trust a write-path 404 to mean "this name is not writable", but you must never trust a read-path 200 to mean "this name is readable". Check the body.
Verified live-probe at build V 2.2.25.220 against both a real-but-read-only
name (FREIGHT_PUMP_CONDENSER_ACTIVE) and an invented one
(EMERGENCY_BATTERIES_MODE_XYZ). Both returned 404.
This also confirms the manifest is ground truth for the writable surface.
FREIGHT_PUMP_CONDENSER_ACTIVE was the one outstanding candidate for the live
surface being wider than the manifest advertises, since nathanctech's client
POSTs to it. It is not writable. That client's code path has never worked.
Read-back is the only source of truth. A 200 certifies nothing about effect, value acceptance, or target existence.
Note the fifth row especially: a failed read is a 200 with an error sentence
in the body, not a 404. If your client checks resp.ok and parses the body as a
value, it will happily ingest the string The readable variable 'X' does not exist. as data.
Omitting Content-Length produces HTTP 411, not a 400 or a helpful message.
Send an explicit empty body even for trigger writes that have no real payload.
curl -s -X POST --data "" "http://localhost:8785/?Variable=...&value=..."In Python: data=b"", not data=None.
A GET on those exact names returns the does-not-exist sentence. This does not
mean the effect is unobservable, it means the read-back lives under a
different name: you write MSCV_0_OPENING_ORDERED and read
MSCV_0_OPENING_ACTUAL.
Combined with "200 certifies nothing", this is the defining constraint on writing a correct client. The full mapping is the Read-back column in writable-variables.md.
The manifest's GET and POST lists genuinely differ in membership. POST is not a subset of GET. Verified by probing all 91 individually against a running game: 35 readable, 56 write-only, zero disagreement with the manifest.
RODS_POS_ORDERED and RODS_STATUS are both in the 332-entry GET list and both
return an empty body, while RODS_POS_ACTUAL right next to them returns 68.
So "in the GET list" does not imply "returns a value". Treat empty as a distinct outcome from both a value and the does-not-exist sentence. Three-state parsing, not two.
CORE_STATE returns:
REACTIVO/NOREACTIVOfrom a single?Variable=CORE_STATEGET1/0fromWEBSERVER_BATCH_GET
Same underlying variable, endpoint-dependent representation. Any client reading both endpoints has to normalise. This is not documented anywhere upstream and is an easy source of silent type confusion.
CONDENSER_VACUUM_PUMP_MODE reads back OPERACIONAL but you write
OPERATIONAL. The write vocabulary and the read vocabulary are different
languages for one variable.
Enum values generally are Spanish: REACTIVO, NOREACTIVO, INACTIVO,
INICIANDO, GENERANDO, PRESURIZANDO, AUTOMATICO. Valve identifiers are
Spanish. Variable names are English. Do not assume a language from context.
There is no single canonical boolean form, and clients disagree:
| Form | Where |
|---|---|
True / False |
what auto_nuke reads (auto_nuke:lib/auto_nuke/api.ex:40-41 pattern-matches exactly these) |
true / false |
what auto_nuke writes, via Elixir to_string(true) |
TRUE / FALSE |
nathanctech's literal pool |
1 / 0 |
FREIGHT_PUMP_CONDENSER_ACTIVE posted as int (nathanctech:Condenser.cs:56) |
Live reads at build V 2.2.25.220 return title case (True / False). Since
writes are silently discarded on mismatch rather than rejected, a casing error
here is invisible until you read back.
GHXX:Controllers/CondenserController.cs:18 formats outgoing floats with
.ToString("N2") and no explicit CultureInfo. Under a comma-decimal locale
that emits 12,34 instead of 12.34.
Status: unverified against the live game. Flagged as a hazard to test, not asserted as a defect. If you run a comma-decimal locale and write floats, test this before trusting it.
- Do not sample immediately after a save load. Post-reload
CORE_STATE_CRITICALITYread 5, the historical excursion threshold. Settled values 30 seconds later were 0.64 and falling. - The webserver unbinds on save load and appears to need re-enabling per
save. Check
ss -lntp | grep 8785before assuming your client broke. - Check your sample rate against the signal period. A 1 Hz sample of a roughly 2 second sawtooth yields a confident and meaningless rate.
- The plant is never at rest. Pressure drifts about 0.2 bar every 5 to 10 seconds with no command issued. Any before-and-after diff attributes that drift to whatever you happened to do. See the matched null probe method in scraping.md.
WEBSERVER_BATCH_GET returns 321 variables in one call. Every community client
surveyed polls one variable per request instead. If you are writing a client,
use the batch endpoint, subject to the type-difference caveat above.