Projects and Management membership
A project groups encrypted drops under one shared Management membership.
Create a project, retain its data.project.id, then send that value as
project_id on every drop lifecycle request.
Authentication and roles
Send an admitted M7 access token and JSON body. Project/drop routes use the
personal workspace; organization headers and selectors fail with HTTP 400.
Authority requires the active, pinned Management association and active caller
membership. Another group's membership or a service admin/root role grants
no project authority. Each operation also requires its exact current capability.
| Project role | Allowed actions within its capability profile |
|---|---|
owner |
Read and mutate drops, rename the project, manage memberships and privileged roles, and delete the project. |
admin |
Read and mutate drops, rename the project, and manage member/viewer memberships. |
member |
Read and mutate drops. |
viewer |
Read project/drop metadata and keyed payloads; leave its membership. |
Every role can leave. The final active owner cannot leave, be disabled,
removed, or demoted. The capabilities returned by the project context are
the server's current action list; use them to enable UI controls.
Routes and fields
All routes use POST. Unknown fields and caller-supplied authority are rejected.
project_id and principal_id are non-nil dashed UUIDs.
| Route | Required fields | Optional fields and result |
|---|---|---|
/v1/project/create |
name |
description; creates the project and makes the caller owner. Returns project context. |
/v1/project/list |
None | q, limit, offset; returns accessible projects. |
/v1/project/get |
project_id |
Boolean include_deletion_summary; returns project context and an authorized deletion summary when requested. |
/v1/project/update |
project_id, name |
description; owner/admin plus project.update required. |
/v1/project/delete |
project_id, confirmed: true, summary |
Owner plus project.delete required; cascades to every project drop. |
/v1/project/member/list |
project_id |
Owner/admin plus project.member.list; returns data.items. |
/v1/project/member/add |
project_id, principal_id |
role, default member for a new membership; target must already be an active receiving principal. |
/v1/project/member/update |
project_id, principal_id, at least one change |
role and/or status (active or disabled). |
/v1/project/member/remove |
project_id, principal_id |
Marks membership removed and immediately ends its access. |
/v1/project/member/leave |
project_id |
Removes only the caller's membership. |
name is trimmed text of 1–191 bytes without control characters.
description is nullable text up to 8000 bytes; whitespace-only text becomes
null. Omission on update retains the old description. Listing accepts q
up to 191 bytes as a project-name search, limit from 1–100 (default 20), and
nonnegative offset (default 0). Results contain items, total_count,
limit, offset, nullable next_offset, and principal_capabilities.
Each item contains project fields, the caller's role, and capabilities.
An admin cannot create, change, or remove owner/admin memberships. Privileged
assignments also require project.member.assign_roles. Re-adding a removed
principal reactivates its membership ID with active status and retains its role
unless a new role is supplied; membership history
is preserved. Membership results contain data.member with id, principal,
role, status, timestamps and optional identity (id, name, kind).
The member list additionally supplies can_edit per row.
Create and select a project
curl -sS https://api.drop.service.m7.org/v1/project/create \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"name":"Example handoffs","description":"Short-lived payloads"}'
{
"status": 1,
"comment": "OK",
"data": {
"project": {
"id": "PROJECT_UUID",
"name": "Example handoffs",
"description": "Short-lived payloads"
},
"management_group_id": "GROUP_UUID",
"membership": {"id":"MEMBERSHIP_UUID","principal":"PRINCIPAL_UUID","role":"owner","status":"active"},
"capabilities": ["project.get", "drop.store", "project.delete"]
}
}
The example abbreviates project fields and capabilities. Project records also
return created_by, updated_by, created_at, and updated_at.
Review and confirm cascade deletion
First call /v1/project/get with project_id and
include_deletion_summary: true. An authorized owner receives
data.deletion_summary containing six nonnegative integer counts: total,
active, reserved, consumed, revoked, and expired. Show this summary
to the user and send the exact object back as summary with confirmed: true:
{
"project_id": "PROJECT_UUID",
"confirmed": true,
"summary": {"total":2,"active":1,"reserved":1,"consumed":0,"revoked":0,"expired":0}
}
The service compares the reviewed counts under the project lock before deleting
all drops and retiring the project's associations. A changed summary returns
HTTP 409; fetch and review a new summary before confirming again. Success
returns project_id, deleted: true, and deleted_drops with the final counts.
Missing confirmation or malformed counts return 400. Missing projects return
404; denied membership/capability returns 403; a busy project mutation
returns 409 and can be retried after reloading context.