---
title: "Add a project member"
method: POST
path: "/v1/projects/{project_id}/members"
tags: ["Projects"]
---

# Add a project member

`POST /v1/projects/{project_id}/members`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/members`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

Adds a colleague to the project by email address. Free and uncapped on every plan: a member adds no run, no channel, no trigger and no byte, so every cost they cause is already billed through the project's owner. Members are counted nowhere.

**The address does not need an account.** Inviting one that has none creates it unverified and records the membership beside it, so the invitee is already a member with status `pending`. Their first sign-in code — which they request themselves, from the link in the invitation — is what makes them `active`. There is no separate acceptance step and no invitation to expire: the address is the credential, and the invitation reached it.

An `owner` may grant `admin` or `member`; an `admin` may grant `member` only, so administration cannot hand out its own authority. `role: owner` is refused — that is the billing owner, and ownership moves only through a transfer the new owner accepts (`PUT /v1/projects/{project_id}/transfer`).

The invitation email is a courtesy, not the grant: if it cannot be sent the membership still stands, and the response still says `pending`. Requires an account-wide credential — a project-scoped key cannot grant access that would outlive its own revocation.

## Request body

- ProjectMemberCreate
  - `email` string, email, required: The colleague's address, lower-cased before lookup. It does not need an account yet.
  - `role` 'admin' | 'member', required: The roles a membership write may name. `owner` is absent by construction: it is the billing owner, so granting it would change who pays, and transferring that is a different act with its own decision.

## Response `201`

The member was added.

- ProjectMember
  - `id` string, required: Public membership ID (pmem_ prefix).
  - `project_id` string, required
  - `user_id` string, required
  - `email` string, email, nullable, required: The member's address; null if the account no longer exists.
  - `name` string, nullable, required
  - `status` 'active' | 'pending', nullable, required: `pending` until the member has signed in for the first time, `active` afterwards. Derived from the account, not stored, so it needs nothing to run when they arrive. Null if the account no longer exists — there is no status to report for somebody who is never arriving.
  - `role` 'owner' | 'admin' | 'member', required: What the member may do in this project. `owner` — everything, including deleting it and paying for it; exactly one per project. `admin` — everything a `member` may do, plus managing who else is in the project and how the project itself is configured. `member` — every read and write on the project's own resources, including minting project API keys. When returned on a `Project`, this is *your* role, not a property of the project: it is what lets a client hide an action the request would refuse.
  - `invited_by_user_id` string, nullable, required: Who added this member; null for the founding owner, who was added by the act of creating the project.
  - `created_at` string, date-time, required

## Other responses

- `400`: Missing `email`, or a `role` outside `admin`/`member`.
- `401`: Missing or invalid credentials.
- `403`: The caller is a `member`, an `admin` granting `admin`, or a project-scoped credential.
- `404`: The resource does not exist (existence is not leaked).
- `409`: That address is already a member of this project.
- `429`: This account has sent too many invitations today.

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
