# Enrollment requests

Request a pairwise connection or resource membership, then accept, decline or
cancel it after your application has checked the user's authority and consent.
Relationship commits the resulting bond and request event together.

## Routes

All routes use POST under `https://api.relationship.service.m7.org/v1/` with
the [Bearer and workspace contract](https://m7.org/docs/api/api.relationship.service.m7.org/authorization.md). Every request names
UUID strings `project_id`, `space_id` and optional `subspace_id`.

| Route | Other required fields | Optional fields | Authority and result inside `data` |
| --- | --- | --- | --- |
| `/projects/relationships/requests/list` | — | `kind`, `count`, `offset` | `relationship.list`; `project_id`, `items`, `next_offset`, `space`; supplied `kind` is echoed. |
| `/projects/relationships/requests/create` | `source`, `idempotency_key`; pairwise also requires `target` | `kind`, `metadata`, `ttl_seconds` | `relationship.manage`, Owner/Admin; `request`. |
| `/projects/relationships/requests/decide` | `request_id`, `version`, `decision` | — | `relationship.manage`, Owner/Admin; `request`. |

Omitted or null `subspace_id` selects the root. References and metadata follow
the [scope contract](https://m7.org/docs/api/api.relationship.service.m7.org/scopes.md). List paging defaults to 100 with `count` 1–200;
only the pending requests in that scope are listed.

## Create a request

| Field | Type and behavior |
| --- | --- |
| `source` | Required principal object. |
| `target` | Required principal object for pairwise; omit for resource membership. |
| `kind` | Pairwise: `connection` (default). Membership: `invitation` (default) or `application`. Set it explicitly for clarity. |
| `idempotency_key` | Required nonempty UTF-8 string, at most 191 bytes; keep it for retries of this attempt. |
| `metadata` | Optional JSON object, defaults to `{}`; no built-in consent or resource-permission semantics. |
| `ttl_seconds` | Optional integer 1–31,536,000, default 604,800 (seven days). |

```bash
curl --fail-with-body --silent --show-error \
  --request POST 'https://api.relationship.service.m7.org/v1/projects/relationships/requests/create' \
  --header 'Authorization: Bearer ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{"project_id":"PROJECT_UUID","space_id":"SPACE_UUID","subspace_id":"SUBSPACE_UUID","source":{"namespace":"example.app","principal_id":"PERSON_UUID"},"kind":"application","idempotency_key":"example-application-attempt-1","metadata":{"message":"Please add me"},"ttl_seconds":604800}'
```

The returned request includes `id`, scope IDs, `kind`, `state`, `source_ref`,
`target_ref`, `metadata`, `expires_at`, `version` and nullable
`relationship_id` and `decision_actor`. Save the actual ID and positive integer
version. New requests have `state: "pending"`; membership `target_ref` is null.

There is at most one pending request for the exact bond and kind. Invitation
and application slots are independent; pairwise has one connection slot.
Another pending request in that slot returns body code 409.

## Retry identity

An idempotency key is scoped to the project and actual authenticated actor,
across relationship kinds. The same key and same input return the prior result,
including a terminal result. Changed input returns body code 409. Keep the
same actor for a retry; switching authenticated actors changes retry identity.
Use a new key for a new attempt after decline, cancellation or expiry.

## Decide and observe completion

```json
{
  "project_id": "PROJECT_UUID",
  "space_id": "SPACE_UUID",
  "subspace_id": "SUBSPACE_UUID",
  "request_id": "REQUEST_UUID",
  "version": 1,
  "decision": "accept"
}
```

`decision` is exactly `accept`, `decline` or `cancel`. `request_id` is a UUID;
`version` is its current positive integer version.

| Outcome | Returned terminal state | Relationship result |
| --- | --- | --- |
| `accept` | `accepted` | Creates or reuses the active bond; `relationship_id` identifies it. |
| `decline` | `declined` | No bond created. |
| `cancel` | `cancelled` | No bond created. |
| Lifetime elapsed | `expired` | No bond created; a decision returns body code 409. |

Terminal requests leave the pending list. Their results remain in activity and
retry receipts; a terminal request is not reactivated. Retrying a decision
with the same terminal outcome returns that result. A conflicting terminal
decision returns body code 409. A stale version also returns 409; missing or
wrong-scope IDs return 404. Observe both `status` and `data.code` under the
[response rules](https://m7.org/docs/api/api.relationship.service.m7.org/README.md#requests-and-responses).

Request-list and activity reads, and relevant commands, settle elapsed requests.
Mailbox reads do not settle expiry. Use the [mailbox](https://m7.org/docs/api/api.relationship.service.m7.org/mailbox.md) to deliver
result events and the [current graph](https://m7.org/docs/api/api.relationship.service.m7.org/relationships.md) for current state.
Acceptance is not an automatic permission change in your application;
an accepted event can remain after its bond is removed.
