Projects, membership and capabilities

A Post project contains message groups, templates, delivery policies, and jobs. Choose a workspace, then create or select a project. Project UUIDs are independent of user and organization principal UUIDs.

Authentication and project scope

All routes below use POST and require a Bearer access token, current service-list admission, and the named action. Project-specific routes require the effective workspace principal's active Management membership. Service Admin/Root does not bypass that membership. Organization grants further restrict project grants; see Authorization.

Every /v1/members/policy/*, /v1/members/message/*, and /v1/members/job/* request requires project_id (a non-nil UUID). The API derives resource scope from the authorized project and rejects owner, project, principal_id, and actor_id overrides on those routes. Resource IDs and names are resolved inside that project, even when a caller belongs to multiple projects. A message group can also link a service-provided shared template by its known ID; this does not grant cross-project access.

Project routes

Route Action Required fields Optional fields / result
/v1/projects/list project.list None items with project contexts; can_create.
/v1/projects/create project.create name Nonempty string, at most 255 bytes; returns new project context.
/v1/projects/context project.view project_id include_deletion_summary boolean, default false; a true value requires Owner and project.delete.
/v1/projects/delete project.delete, Owner project_id, matching confirm_project_id Deletes an eligible project and its project content atomically.

Unknown project fields are rejected. Creation assigns the effective principal as the initial active Owner and records the real actor separately. Context contains project, membership, management_group_id, and primitive capabilities. Membership has id, principal, role, and status. Use the current capabilities to offer actions; every action is rechecked on the server.

curl -sS https://api.post.service.m7.org/v1/projects/create \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Transactional messages"}'

Store data.project.id, then select that project on delivery requests:

{
  "project_id": "PROJECT_UUID",
  "purpose": "Confirm an email address",
  "proof_policy": "single",
  "message_group": "email-proof",
  "email": ["recipient@example.test"]
}

Membership routes

Member writes use a collaborator's local roster principal_id. Do not send a native user or organization ID. Target principals must be active and admitted.

Route Action Required fields Optional fields / result
/v1/projects/members/list project.member.list project_id limit (1–100, default 50), offset (0–1,000,000, default 0); members, total_count, nullable next_offset, current roles and Owner count.
/v1/projects/members/add project.member.add, Owner/Admin project_id, principal_id Adds an active ordinary Member; existing membership returns 409.
/v1/projects/members/save project.member.save project_id, principal_id Existing member_id; role, status; returns saved, member, and nullable updated context.
/v1/projects/members/remove project.member.remove project_id, principal_id, exact member_id Returns removed and nullable updated context.
/v1/projects/members/leave project.member.leave project_id Current workspace only; returns left. Target identity, role, and status overrides are rejected.
/v1/projects/members/capabilities/list project.member.list project_id Read-only tags and roles.
/v1/projects/members/capabilities/save project.member.save project_id, principal_id, exact member_id, mode, tag_ids Returns saved and updated context.

Roles are owner, admin, and member; editable statuses are active and disabled. Owner manages all levels; Admin manages ordinary Members. Role changes and edits/removal of privileged members additionally require project.member.assign_roles. New memberships default to active Member. Omitted role/status preserve an existing membership's state. The final active Owner cannot be disabled, demoted, removed, or leave. Authority is rechecked when applying each membership change.

Member results include safe principal name/kind/status, capabilities_mode, selected capability_tag_ids, can_update, can_remove, can_leave, can_edit_capabilities, allowed_roles, and allowed_statuses. After a self-change, context: null means the caller lost access; discard old context.

Capability selections

Load the project's read-only catalogue and use its tag_id values. Each tag has capability, name, and description. The role data supplies current defaults and allowed selections.

{
  "project_id": "PROJECT_UUID",
  "principal_id": "COLLABORATOR_PRINCIPAL_UUID",
  "member_id": "MEMBERSHIP_UUID",
  "mode": "custom",
  "tag_ids": ["CAPABILITY_TAG_UUID"]
}

mode is defaults or custom. Use tag_ids: [] with defaults. Custom with an empty list intentionally selects no optional delivery actions. IDs must be unique and valid for the current target role. A role change clears its old custom assignments. Protected management rights and last-owner safeguards remain tied to the role. For organizations, both protected and optional grants are intersected with current verified Management grants.

Public capability discovery

GET /v1/capabilities.json returns the current read-only service capability catalogue without authentication. It is a raw JSON document, outside the usual response envelope, with schema_version, service, catalogue_version, families, capabilities, presets, and role_defaults. Use capability id values to understand action names and profile expansions. Catalogue definitions do not grant the caller those actions; the selected resource context remains the access decision. An unavailable catalogue returns 503.

Deletion and failures

Load context with include_deletion_summary: true before deleting. Authorized Owners receive counts for jobs, policy groups, message groups, templates, DNE entries, and domain rules, plus can_delete. A project containing any job cannot be deleted (409), preserving delivery and proof history. Counts alone do not authorize deletion; the server rechecks membership and jobs at commit.

Invalid UUIDs, selectors, field names, or capability IDs return 400. Missing membership or action returns 403. A missing project returns 404. Existing membership, final-owner conflicts, and jobs blocking deletion return 409. An unavailable catalogue or receiver authorization fails closed.