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.