The JSON API

Everything udd can do is exposed as JSON under /api. The debug console is a client of this API and nothing more.

Requests and responses are application/json. There is no authentication: anyone who can reach the port has full control of the directory.

Object payload #

Endpoints that return an object use one shape:

{
  "id": "9f2c…",                 // GUID (identity)
  "parent": "a01b…",             // GUID or null (root only)
  "name": "Jack",
  "class": "user",
  "classId": "c3d4…",
  "system": false,
  "facets": ["described", "person", "securityPrincipal"],   // effective surface
  "dn": "CN=Jack,OU=Sales",      // derived
  "path": "/Sales/Jack",         // derived
  "hasChildren": false,
  "stage": "alive",              // stored lifecycle stage (alive/deleted/shredded)
  "effectiveStage": "alive",     // stage after apply-time aging
  "ghost": false,                // class definition not yet in this node's schema
  "existence": { stamp },      // the existence atom's stamp (creation + stage transitions)
  "placement": { stamp },      // the (parent, name) atom's stamp
  "usnChanged": 51,              // node-private change-scan index
  "referencedBy": [              // the back-reference index: who points here
    { "id": "77aa…", "path": "/Admins", "attribute": "member" }
  ],
  "attributes": [
    { "name": "member",
      "values": [
        { "value": "b7e1…",
          "meta": { "expiry": ["2027-01-01T00:00:00Z"] },   // only when present
          "refPath": "/Sales/Jack",                         // reference syntax only; null unless target alive
          "refState": "alive",                              // alive / deleted / shredded / missing
          "stamp": { stamp } }                            // this value atom's stamp
      ],
      "absent": [                                           // only when present
        { "key": "c9a2…", "stamp": { stamp } }            // stamped residue of a removed value
      ] }
  ]
}

refPath appears only on values of reference-syntax attributes: the target's current native path, or null if the reference dangles.

Every merge atom — existence, placement, each attribute value — carries a stamp, the replication record that decides merges:

{
  "version": 2,                          // bumped per originating write to this atom
  "at": "2026-08-14T04:01:12Z",          // originating wall-clock time
  "origin": "e8a2…",                     // originating invocation id
  "cert": { "invocation": "e8a2…", "usn": 51 },   // transfer certificate (enumeration coordinate)
  "localUsn": 51                         // node-private
}

absent lists absence atoms: values (or, for a single-valued attribute, the whole attribute — key: null) that were removed and persist as stamped residue so the removal can win future merges. Rewriting an attribute with an unchanged value does not restamp it; multi-valued value sets are unordered and render in canonical (set-key) order.

Objects under a derived repair additionally carry conflicted / orphaned / cycleBroken flags, plus effectiveName (the CNF display name) and effectiveParent where they differ from the stored facts; dn and path always render the effective view. Child summaries carry name (effective), ghost, and the three repair flags.

Endpoints #

Method & pathPurpose
GET /api/nodeThis node's replication identity and sync state: {nodeId, invocationId, usn, hasRoot, purgeHorizon, morgueSize, rollbackLatched, erasureDebt, storage, boot, machine, utd, watermarks}storage carries the durability marks (generation, durable vs appended bytes and USNs, dead flag), boot the boot report, machine this machine's identity ({object, machineKey} — the machine object's GUID and public key).
GET /api/journalThe node's observability journal (newest last, capped, survives restarts): {at, kind, detail} — merges that discarded concurrent input, refused records and pulls, derived repairs, rollback events.
GET /api/storageThe storage view: generation files with sizes, durability marks, rotation threshold, erasure debt, background-rebuild status and last outcome.
GET /api/fabricThe fabric view: this machine's identity and GIP bind, every discovered peer with its link state (idle/dialing/connected/viaInbound/backoff), live QUIC stats (RTT, congestion window, lost packets, MTU, wire bytes), inbound connections (each carrying machine {guid, name} once identified via gip/ident, null while unverified), channels (live channels: connection, peer, service, direction, age, bytes) and recentChannels (a ring of finished channels and failed opens with duration, bytes, and outcome), the supervisor's config, and a ring of timestamped fabric events.
POST /api/fabric/redial/{guid}Debug: clear a peer's backoff and dial it now.
GET /api/msgsThe machine-talk log, oldest first (RAM only — capped at 200 records, gone on restart): each record is {atMs, direction (in/out), peerGuid, peerName, text, outcome}, where sent records carry outcome delivered (+rttMs), failed (+detail), or unconfirmed (+detail), and received records carry received. {enabled: false} on a node without a fabric.
POST /api/msgs/sendSend one ud/msgs message and block for its outcome (bounded: 5 s channel-open + 10 s ack). Body: {"to", "text"}to is a machine GUID or a caseless machine name resolved against the fabric's targets; text is 1–16384 bytes. HTTP errors are for request problems only (422 unknown-recipient, 422 bad-text, 503 no-fabric); a send that ran returns 200 carrying the finished record, including failed and unconfirmed outcomes — those are data, not transport errors.
POST /api/storage/rotateDebug: seal the active WAL, open the next generation, kick a background rebuild.
POST /api/storage/rebuildDebug: kick a background snapshot rebuild (no rotation).
GET /api/rootThe root object.
GET /api/morgueEvery tombstone: {id, name, class, stage, effectiveStage, at, lastPath}.
POST /api/gcRun local GC now (harden aged tombstones, purge expired ones); returns the new horizon.
POST /api/objects/{id}/restoreRestore a soft-deleted object (top-down; refuses conflicts and elapsed windows).
POST /api/objects/{id}/shredSecurity-erase (subtree-wide from alive, or escalate an existing soft delete).
GET /api/objects/{id}One object by GUID.
GET /api/objects/{id}/childrenChild summaries, in caseless name order: {id, name, class, system, hasChildren}.
POST /api/objects/{id}/childrenCreate a child. Body: {"name", "class", "attributes"?}attributes maps attribute name → value list and is applied atomically with the create (definitions are born whole). Returns 201 + the object.
PATCH /api/objects/{id}Rename and/or move (ModifyDN shape). Body: {"name"?, "parent"?}, parent by GUID.
PUT /api/objects/{id}/attributes/{name}Replace an attribute's values wholesale. Body: {"values": [{"value", "meta"?}]}. An empty values deletes the attribute. Returns the updated object.
DELETE /api/objects/{id}Soft-delete the object and its whole subtree (restorable tombstones). Returns 204.
GET /api/path and GET /api/path/{path}Resolve a native untyped path (caseless) to its object: /api/path/Sales/Jack. Bare /api/path is the root.
GET /api/schemaThe compiled schema summary (below).

Attribute names in URLs and bodies are caseless and accept wire names (Acme-badgeColor); the stored spelling is always the schema's canonical one.

Every mutating endpoint acknowledges only after the write is durable — a 200/201/204 means the disk holds it. Attribute values are capped at 1 MiB each (large blobs are a future side-store's job, not an atom's).

The schema summary #

GET /api/schema returns the compiled schema, sorted by name:

{
  "attributes": [ { "id", "name", "syntax", "singleValued", "matching", "aliases", "legalMeta" } ],
  "facets":     [ { "id", "name", "attributes": [ { "name", "required" } ] } ],
  "classes":    [ { "id", "name", "dnPrefix", "instantiable", "facets" } ],
  "extensions": [ { "id", "name", "targetClass", "facets" } ],
  "quarantined": [ { "id", "name", "reason" } ]
}

quarantined lists definitions excluded from the effective schema — replication can deliver a facet before its attributes. Compilation is total: it never fails, it quarantines what cannot join yet (with the reason) and lets it join automatically when its dependencies arrive. References to tombstoned definitions compile out instead (the facet keeps working minus its dead member). Originating schema writes are still strictly gated: a local write that would grow the quarantine is refused with schema-violation.

Errors #

Failures return {"error": "<code>", "message": "<human text>"} with:

CodeStatusMeaning
bad-object-id400Path segment is not a GUID.
no-such-object404Object (or path/reference target) does not exist.
no-root503A joined store that has not yet received its first sync has no root.
name-in-use409Sibling name taken (caseless, type-blind).
invalid-name422Name fails validity rules (or definition-name grammar).
unknown-class422No class by that name.
class-not-instantiable422Class is marked non-instantiable (domainRoot, configuration).
unknown-attribute422No attribute definition by that name.
attribute-not-permitted422Attribute is outside the object's surface.
single-valued422Multiple values for a single-valued attribute (or meta).
syntax-violation422Value fails its syntax (bad integer/boolean/timestamp, non-GUID or dangling reference).
illegal-meta-key422Meta key unknown or not in the attribute's legalMeta.
duplicate-value409Value set contains duplicates under the matching rule.
unknown-facet422attachedFacets value is not a facet definition.
facet-has-values409Facet detach / extension delete refused while exclusively-provided attributes hold values.
already-deleted409Mutation aimed at a tombstone (restore first, or leave the dead be).
ghost-object409The object's class isn't in this node's schema yet; originating writes refuse until sync converges.
rollback-quarantine409The node detected it is running rolled-back (restored) state: its invocation was re-minted and originating writes are suspended until a completed pull heals it.
not-restorable409Restore refused: wrong stage, elapsed window, or parent not alive.
root-is-protected409The root cannot be deleted, renamed, or moved.
system-object-protected409System objects refuse moves and deletion (their attributes are writable).
invalid-schema-placement422Wrong class/parent combination in or around the schema subtree.
schema-definition-immutable409Definitions are add/delete only.
definition-in-use409Unused-delete rule refused the deletion; the message enumerates the blockers by name.
schema-violation422Trial recompile failed; the write was rolled back (message carries the compiler's reason).
would-create-cycle409Move would place an object under itself or a descendant.

Edit this page