# Edges

Declare, replace, list and revoke directed trust.


**Auth:** Source-device signed edge certificate.
## 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/edges/request HTTP/1.1
Host: trust-staging.rebellion.systems
Content-Type: application/json

{
  "signerGraphKeyId": "<source-node-id>",
  "subjectGraphKeyId": "<target-node-id>",
  "signingGraphKeyId": "<source-device-id>",
  "trust": 80,
  "scope": "identity",
  "createdAt": "<current-ISO-timestamp>",
  "nonce": "<fresh-random-nonce>",
  "signature": "<base64-edge-signature>"
}
```

Response shape:

```json role="response"
{
  "edge": "<active-edge-object>",
  "graphEpochRoot": "<graph-root>",
  "poseidonEpochRoot": "<poseidon-root>",
  "eventLogRoot": "<log-root>",
  "eventLogSize": "<nonnegative-integer>"
}
```




A declaration is a one-way score from one node to another, active immediately. Scores are integers from 0 to 100. New declarations use `scope: "identity"`; this is a protocol compatibility constant, not a relationship label.

## Declare trust

`POST /v1/edges/request` accepts a signed edge certificate:

```json role="request"
{
  "signerGraphKeyId": "source-node-id",
  "subjectGraphKeyId": "target-node-id",
  "signingGraphKeyId": "source-device-id",
  "trust": 80,
  "scope": "identity",
  "createdAt": "2026-11-04T12:00:00.000Z",
  "nonce": "fresh-random-nonce",
  "signature": "base64-edge-certificate-signature"
}
```

`expiresAt` is optional. All timestamps must parse. Sign the certificate fields using the SDK's edge signing helper; `signature` and `signingGraphKeyId` are not certificate preimage fields. Include `signingGraphKeyId` when the signing device ID differs from the source node ID.

Success is 201 with `{edge, graphEpochRoot, poseidonEpochRoot, eventLogRoot, eventLogSize}`. A fresh declaration for the same direction atomically supersedes the previous one. Public edge projections omit the certificate signature and nonce.

## Conditional replacement

`POST /v1/edges/replace` accepts the same body but requires the signed nonce:

```text
replace-v1.<current-edge-id>.<random>
```

The random suffix is 8–128 letters, digits or hyphens. The declaration applies only if the named edge is still current. This is the preferred route when a user edits a score they previously read.

| Code | Meaning |
| --- | --- |
| 400 `EDGE_REPLACE_NONCE_REQUIRED` | Replacement nonce missing |
| 400 `EDGE_NONCE_INVALID` | Replacement nonce has an invalid shape |
| 400 `EDGE_REPLACE_ROUTE_REQUIRED` | Conditional certificate sent to `/request` |
| 409 `EDGE_SUPERSEDED` | Another declaration is current; response includes `currentEdgeId` |
| 409 `EDGE_NOT_CURRENT` | No declaration currently stands |

A refused conditional certificate consumes its nonce; a wrong-route refusal does not. Refresh state and sign anew. If `/replace` returns 404 because it is unsupported, stop the conditional operation.


**Never**
Do not fall back from `/replace` to `/request`: an unconditional declaration does not preserve the user's condition.

## List incident edges

`POST /v1/edges/list` takes `{graphKeyId, authorization}`, with read operation `list-edges`. Here the query's `graphKeyId` is the **own node ID**, while `authorization.graphKeyId` is its signing device. The response is `{edges, edgeTags}`.

Incoming and outgoing edges are included. Outgoing scores are visible to the owner; incoming projections do not disclose the other source's private score. Private tags appear only on supported current outgoing edges. Optional handle and deactivation fields help display already-visible endpoints. `GET /v1/edges` always returns 401.

## Revoke and legacy confirm

`POST /v1/edges/:edgeId/revoke` takes `{action, signature}`. Sign action `revoke-edge` with `graphKeyId`, `edgeId`, `nonce` and `signedAt`. Only a usable device of the source node can revoke its declaration. The response contains `edge` and the epoch commitment fields. Removing a superseded edge can return `EDGE_SUPERSEDED`; refresh before asking the user to remove the current declaration.

`POST /v1/edges/:edgeId/confirm` remains for legacy clients. It takes the corresponding signed `confirm-edge` action from the subject node's device. On active trust it is an authenticated no-op; it does not create a reciprocal declaration. Revoked trust cannot be confirmed.


**Does not establish**
Receiving a vouch does not mean you vouch back. Device expiry and revocation do not erase node connections; edge expiry and source revocation determine whether the declaration remains usable.
