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. 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. 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). |
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
{
"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.
Request-list and activity reads, and relevant commands, settle elapsed requests. Mailbox reads do not settle expiry. Use the mailbox to deliver result events and the current graph for current state. Acceptance is not an automatic permission change in your application; an accepted event can remain after its bond is removed.