Mousehole provides an API for programmatic access to its functionality.
Protected endpoints accept a Bearer token. Set the MOUSEHOLE_AUTH_TOKEN
environment variable (or its _FILE variant) and
pass it in the Authorization header:
Authorization: Bearer <token>
Example with curl:
curl -H "Authorization: Bearer mytoken" http://localhost:5010/stateA request with a missing or invalid credential is rejected with 401 and a
WWW-Authenticate header (see Errors).
GET /state, PUT /cookie, and POST /updates all return the same public
state object. It never contains your cookie.
Mousehole records each exchange with MAM as a contact; when a cookie is set,
the contact performs an IP update (the ipUpdate field below).
lastMamContact is a tagged union on reached:
- Reached, with a cookie — has
ip/asn/asand anipUpdate(httpStatusis200ok,429throttled,403rejected;msgis MAM's text, for display only). - Reached, no cookie yet (setup lookup) — has
ip/asn/as, noipUpdate. - Unreachable (transport failure) — no host fields:
{ "at": "…", "reached": false, "error": { "type": "timeout-error", "message": "Request to … timed out after 10s. Is the network up?" } }
HTTP boundary rejections, invalid requests, and server-side disk faults return a non-2xx status with this envelope:
{
"type": "json-parse-error", // stable, machine-readable tag
"message": "...", // human-readable, written to be actionable
// Only on a schema-error: the field-level validation problems.
"issues": [{ "path": "value", "message": "..." }],
// Present when this error wraps a lower-level one.
"cause": { "type": "...", "message": "..." },
}type |
Status | When |
|---|---|---|
host-not-allowed |
403 | Host header missing or not in MOUSEHOLE_ALLOWED_HOSTS |
authentication-required |
401 | missing or invalid token/session |
auth-not-configured |
500 | no MOUSEHOLE_AUTH_* credential is set |
origin-not-allowed |
403 | disallowed cross-origin browser request (waived for token auth) |
unsupported-media-type |
415 | request body without Content-Type: application/json |
payload-too-large |
413 | request body over 8 KiB |
schema-error |
400 | request body fails schema validation (carries issues) |
json-parse-error |
400 | request body given by client is invalid JSON |
json-parse-error |
500 | MAM response or state file is invalid JSON (see message) |
file-read-error |
500 | the state file exists but cannot be read (e.g. permissions) |
file-write-error |
500 | the state file cannot be written |
directory-create-error |
500 | the state directory cannot be created |
not-found |
404 | no matching route |
unhandled-error |
500 | any otherwise-unclassified server error |
Boundary failures (401, 403) can occur on any endpoint marked Requires
Auth. The state-file 500s can occur on any endpoint that reads or writes
state (GET /state, PUT /cookie, POST /updates, GET /health).
Requires Auth?: Yes
A pure read of the current state. Returns the state shape.
Failure modes:
401/403if the request isn't authorized500(file-read-errororjson-parse-error) if the persisted state cannot be read
Requires Auth?: Yes
Set the MAM session cookie. This stores the credential and immediately
contacts MAM with it, so the response body reflects whether the cookie works
(e.g. a 403 rejection shows up right away). Returns the
state shape.
Example request body:
{ "value": "<your-mam_id-cookie-value>" }value must be a non-empty string. The cookie is persisted regardless of the
MAM outcome: a rejection (403), a throttle (429), or an unreachable MAM is
recorded in the returned lastMamContact, and the response is still 200. The
HTTP status reflects request handling and persistence only, never what MAM said.
Failure modes:
415if the body isn'tapplication/json413if it exceeds 8 KiB400for malformed JSON (json-parse-error) or a missing/emptyvalue(schema-error, withissues)401/403if unauthorized500if state cannot be read or written
Requires Auth?: Yes
Run an update now: contact MAM and persist the result. Takes no body. Returns the state shape.
Like PUT /cookie, a failed MAM contact is recorded, not raised: the response
is 200 with the outcome in lastMamContact.
Failure modes:
401/403if unauthorized500if state cannot be read or written
Requires Auth?: No
Answers with 200 if the server is up and able to read its persisted state.
Therefore, you can infer liveness from the status code. This may be suitable
for container orchestrators looking to know if the container should be
restarted.
To facilitate monitoring, the body reports a state-like response, but it is reduced because this endpoint does not require authorization.
{ "lastMamContactResult": "ok" } // synced
{ "lastMamContactResult": "rejected" } // needs attention
{ "lastMamContactResult": "unreachable" } // MAM is down or network is downThose are a few illustrative values; the table below lists the complete set.
lastMamContactResult |
Contact Attempted? | Contact Success? | IP Update Attempted? | IP Update Success? | Notes |
|---|---|---|---|---|---|
pending |
❌ No | N/A | N/A | N/A | No contact attempt has run yet. |
unreachable |
✅ Yes | ❌ No | N/A | N/A | Unable to contact MAM |
no-cookie |
✅ Yes | ✅ Yes | ❌ No | N/A | User has not yet set a cookie. |
rejected |
✅ Yes | ✅ Yes | ✅ Yes | ❌ No | Cookie refused by MAM; update not applied. |
throttled |
✅ Yes | ✅ Yes | ✅ Yes | ❌ No | Update refused: last change too recent. |
ok |
✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | IP update succeeded. |
Failure modes:
500if state cannot be read
Requires Auth?: Yes
A
Server-Sent Events
stream the web UI subscribes to. The events have no content; they just signal
"something has changed, re-pull GET /state". Used by the dashboard to update
live without polling. Not generally useful to API clients.
Failure modes:
401/403if the request isn't authorized
{ "hasCookie": true, // is a MAM cookie configured? "hasAuth": true, // is the web UI password-protected? (frontend concern) // When the next automatic contact is scheduled (RFC 9557). Omitted until the // scheduler has armed the first interval, e.g. before the initial contact // completes (and after shutdown). "nextContactAt": "2025-06-21T14:27:28.113-05:00[America/Chicago]", // The most recent contact with MAM. Absent until the first check runs. "lastMamContact": { "at": "2025-06-21T14:22:28.111-05:00[America/Chicago]", "reached": true, // we got an HTTP response from MAM "ip": "123.123.123.123", "asn": 12345, "as": "MegaCorp", // Present only when a cookie drove an update (the dynamicSeedbox call). "ipUpdate": { "success": true, "msg": "No change", "httpStatus": 200 }, }, }