# Public handles

Claim, release and resolve an optional node handle.


**Auth:** Own-device signed read, operation `read-own-handle`.
## Request and response

Angle-bracket values describe placeholders, including nested objects and numeric types; these are wire shapes, not captured service data. Replace them before sending.

```http
POST /v1/handles/me HTTP/1.1
Host: trust-staging.rebellion.systems
Content-Type: application/json

{
  "graphNodeId": "<own-node-id>",
  "authorization": {
    "graphKeyId": "<device-id>",
    "nonce": "<fresh-random-nonce>",
    "signedAt": "<current-ISO-timestamp>",
    "signature": "<base64-DER-signature>"
  }
}
```

Response shape:

```json role="response"
{
  "nodeHandle": {
    "graphNodeId": "<own-node-id>",
    "handle": "<handle-or-null>",
    "handleRevision": "<integer>",
    "claimedAt": "<timestamp-or-null>",
    "nextClaimAt": "<timestamp-or-null>"
  },
  "staleBefore": "<server-cutoff-timestamp>"
}
```




A node may hold one optional public handle. Node IDs continue to identify connections and authorize actions. Handles use `lower-ascii-v1`: 3–30 lowercase letters, digits or underscores, beginning with a letter. Normalize before signing; the server never rewrites signed text.

## Read your current revision

`POST /v1/handles/me` takes `{graphNodeId, authorization}` using signed read operation `read-own-handle` and exact query `{graphNodeId}`. A usable own device, including a quarantined one, may read it.

The response is `{nodeHandle, staleBefore}`. The owner view includes `graphNodeId`, `handle`, `handleRevision`, `claimedAt` and `nextClaimAt`. The handle and dates may be null when not set. Use the current revision for any change. `staleBefore` is the server's cutoff for actions too old to be admitted.

## Claim, rename or release

| Route | Signed action fields beyond device ID, nonce and timestamp |
| --- | --- |
| `POST /v1/handles/claim` | `action: "claim-handle"`, `handle`, `expectedRevision` |
| `POST /v1/handles/release` | `action: "release-handle"`, `expectedRevision` |

Both accept `{action, signature}` from a usable approved graph-purpose device. `expectedRevision` is an integer from 0 to 2147483645. Success returns `{nodeHandle, graphEpochRoot, poseidonEpochRoot, eventLogRoot, eventLogSize}` and advances the handle revision. Claiming a different name while holding one is an atomic rename.

A node may claim or rename once every 30 days. Release takes effect immediately. Released or renamed-away handles remain unavailable to everyone for 30 days. Handle changes advance the event log without changing the graph commitment roots.

## Resolve a handle

`POST /v1/handles/lookup` accepts `{graphNodeId, handle, authorization}`. Sign operation `lookup-handle` over `{graphNodeId, handle}`; `graphNodeId` is the requesting device's own node.

Success returns `{nodeHandle: {graphNodeId, handle, claimedAt}}`, where the returned node ID is the resolved holder. Unknown, released and cooling-off names return the same 404 `HANDLE_NOT_FOUND`.

## Errors and uncertain outcomes

| Code | HTTP |
| --- | --- |
| `HANDLE_INVALID`, `HANDLE_RESERVED` | 400 |
| `HANDLE_UNAVAILABLE`, `HANDLE_UNCHANGED`, `HANDLE_NOT_CLAIMED`, `HANDLE_REVISION_CONFLICT` | 409 |
| `HANDLE_CHANGE_LIMIT` | 429 |
| `HANDLES_DISABLED`, `HANDLE_PERSISTENCE_UNAVAILABLE` | 503 |

After a lost response, use `/handles/me`. The intended name at revision + 1 establishes success; any different revision means the old action can no longer apply. An unchanged revision is conclusive only after the action can no longer be admitted: its `signedAt` must be earlier than a subsequent `staleBefore`, with a conservative time margin. Then sign a new retry. Do not replay the old action.


**Does not establish**
A handle is a public discovery hint. It grants no authority, proves no real-world identity and can change owners after its cooling-off period. Retain the verified node ID when establishing a connection.
