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.