Skip to content
API Now! is currently in closed beta. We are constantly updating these guides as we release updates!

Match Project Role

The Match Project Role (matchProjectRole) access rule restricts API operations based on the user’s membership and role within a specific Project.

In collaborative platforms (such as task managers, code repositories, design boards, and team workspaces), organizations contain multiple projects, and users are assigned permissions on a per-project basis. A user might be an admin in Project Alpha, but only a viewer or non-member in Project Beta. Match Project Role provides fine-grained access control within these project containers.


1. Organizations vs. Projects: Hard vs. Soft Boundaries

Section titled “1. Organizations vs. Projects: Hard vs. Soft Boundaries”

While both Match Organization Role and Match Project Role manage membership and role access, they serve fundamentally different architectural boundaries:

DimensionOrganization (matchOrganizationRole)Project (matchProjectRole)
Boundary TypeHard Tenancy BoundarySoft Collaboration Boundary
Data IsolationStrict customer isolation. Cross-organization queries are prohibited.Flexible team workspace. Supports cross-project aggregations.
Route Path RequirementMust include the organization container in the URL (e.g. /{orgId}/...).Can be nested (/projects/{projectId}/tasks) or aggregated (/tasks).
Cross-Resource Aggregation❌ Not permitted across organizations.✅ Permitted across all projects the user belongs to.

The Soft Boundary Advantage: Cross-Project Aggregation

Section titled “The Soft Boundary Advantage: Cross-Project Aggregation”

Because projects have soft boundaries, your API can expose two different styles of collection endpoints:

  1. Project-Scoped Endpoints (GET /projects/:projectId/tasks): Retrieves tasks strictly belonging to the single project specified in :projectId.
  2. Aggregated Endpoints (GET /tasks or GET /orgs/:orgId/tasks): Allows a user to fetch all tasks assigned to them across every project they are a member of. The engine automatically filters the results:
    WHERE project_id IN (
    SELECT project_id FROM project_members WHERE user_id = :current_user_id
    )

When a client makes a request to a project-scoped route:

  • Project Context Resolution: The engine identifies the target project from the route path parameter (:projectId) or via cross-project membership for aggregated routes.
  • Membership Verification: The engine checks whether the authenticated user is an active member of that project.
  • Role Verification (Optional): If specific roles are configured, the engine checks whether the user holds one of the permitted project roles.
  • Automatic Data Scoping: For collection queries (like listing tasks), the engine automatically scopes the database query to return only records belonging to matching projects.
PropertySpecification
Rule TypematchProjectRole
Supported StrategiesAll strategies (RBAC, OrganizationRBAC, EnterpriseRBAC)
Allowed Placement LevelsExposed Entity Level, Action Level (Not allowed at global API Model Level)
Execution PhasesPRE_FETCH (create), FETCH (list, search), POST_FETCH (read, update, delete, link, unlink)

To use Match Project Role, your Data Domain must define the following semantic models:

  1. Project: An entity representing the collaboration unit (project/workspace).
  2. User: An entity representing system users.
  3. ProjectMembers: A many-to-many (multiple: true) association connecting Project to User.
  4. ProjectRole: An enum property attached to the ProjectMembers association defining project roles (e.g., ['viewer', 'contributor', 'maintainer', 'admin']).
  5. ProjectResource: A one-to-many association from Project to each project-scoped sub-resource (e.g. Project.tasks, Project.milestones).

When configuring Match Project Role in an Access Scenario, you define:

  • Allowed Project Roles (roles): A list of project roles permitted to execute this action (e.g. ['maintainer', 'admin']).
    • Configured with specific roles: Access is granted only if the user is an active project member AND holds one of the specified roles in that project.
    • Left empty / Unspecified: Any active member of the project is granted access, regardless of their specific project role.

Listing Projects (GET /organizations/:orgId/projects or GET /projects)

Section titled “Listing Projects (GET /organizations/:orgId/projects or GET /projects)”

When users list projects directly, the engine automatically injects a membership subquery:

WHERE id IN (
SELECT project_id FROM project_members WHERE user_id = :current_user_id
)

Users only see the projects they have explicitly joined or been invited to.

Nested Sub-Resource Routes (GET /projects/:projectId/tasks)

Section titled “Nested Sub-Resource Routes (GET /projects/:projectId/tasks)”

When accessing resources nested under a single project:

  1. The engine extracts :projectId from the route.
  2. It verifies that the caller is a member of that project with the required role.
  3. It automatically injects WHERE project_id = :projectId into the database query, preventing cross-project data leakage.

Aggregated Cross-Project Routes (GET /tasks)

Section titled “Aggregated Cross-Project Routes (GET /tasks)”

When an endpoint exposes tasks directly without a :projectId in the path, the engine automatically aggregates records across all projects where the user is an active member:

WHERE project_id IN (
SELECT project_id FROM project_members
WHERE user_id = :current_user_id [AND role IN :allowed_roles]
)

Pattern 1: Standard Team Collaboration (Viewer Read / Contributor Write / Admin Manage)

Section titled “Pattern 1: Standard Team Collaboration (Viewer Read / Contributor Write / Admin Manage)”

Configure progressive access tiers across project sub-resources (such as tasks or documents):

graph TD
    subgraph "Task Entity (/projects/:projectId/tasks)"
        List["GET (List)"] -->|Match Project Role: Any Member| M1["All Project Members"]
        Read["GET /:id (Read)"] -->|Match Project Role: Any Member| M2["All Project Members"]
        Create["POST (Create)"] -->|Match Project Role: contributor, maintainer, admin| W1["Contributors & Admins"]
        Update["PATCH /:id (Update)"] -->|Match Project Role: contributor, maintainer, admin| W2["Contributors & Admins"]
        Delete["DELETE /:id (Delete)"] -->|Match Project Role: admin| Adm["Project Admins Only"]
    end

Pattern 2: Organization Admin Override + Project Member Access

Section titled “Pattern 2: Organization Admin Override + Project Member Access”

In SaaS platforms, organization administrators often need full access to every project within their organization, even if they aren’t explicitly assigned to the project team:

Task.delete Scenarios:
├── Scenario 1 (Organization Owner / Admin Override):
│ └── Match Organization Role: ['admin', 'owner'] <- Org admins can delete tasks in any project
└── Scenario 2 (Project Administrator):
└── Match Project Role: ['admin'] <- Local project admin

The engine evaluates scenarios in order:

  1. If the caller is an Organization Admin, Scenario 1 matches and grants access immediately.
  2. Otherwise, the engine falls back to Scenario 2, checking if the caller is a Project Admin for that specific project.

Caller StateEndpoint ConfigurationResulting HTTP Status
Anonymous (No token)Endpoint requires Match Project Role401 Unauthorized
Valid Member with Allowed RoleUser is an active member with permitted project role200 OK / 201 Created
Valid Member with Insufficient RoleUser is a member, but role is not in roles list (e.g. viewer trying to delete)403 Forbidden
Not a Member of ProjectUser belongs to the organization, but not to :projectId403 Forbidden
Invalid Project IDProject :projectId does not exist in the database404 Not Found