Skip to main content

Invite a colleague

By the end of this tutorial you will have a second person with access to one of your projects, added by email address, with the role you chose — and you will have watched them go from pending to active the first time they sign in.

Members are free and unlimited on every plan, including Free. naturali is priced on runs, not seats.

Four steps:

  1. See who is in the project.
  2. Add your colleague.
  3. Watch them arrive.
  4. Change their role, or let them go.

Every step is one API call, shown for all three clients. The ids in the responses are examples — copy the ones your own calls return.

Prerequisites​

  1. A credential, and an account-wide one. A session JWT from Auth, or a nat_sk_… API key that is not scoped to a single project, exported as NATURALI_TOKEN:

    export NATURALI_TOKEN=nat_sk_...
    export NATURALI_API=https://api.naturali.ai/v1 # curl examples only

    A project-scoped key cannot add members — step 2 answers 403 access_denied with one: the membership would outlive the key that wrote it, so a confined credential would be granting access past its own revocation.

  2. A project you are the owner or an admin of, exported as PROJECT. Your first agent generation builds one, or use any project you already have:

    export PROJECT=proj_V1StGXR8Z5jdHi6B
  3. A colleague's email address. It does not need a naturali account — step 2 explains what happens either way. Use an address you control if you want to follow step 3 yourself.

1. See who is in the project​

Start from what is there. A project you created has exactly one member: you, as its owner.

naturali list-project-members --project-id "$PROJECT"
{
"data": [
{
"id": "pmem_V1StGXR8Z5jdHi6B",
"project_id": "proj_V1StGXR8Z5jdHi6B",
"user_id": "user_V1StGXR8Z5jdHi6B",
"email": "you@acme.com",
"name": "You",
"status": "active",
"role": "owner",
"invited_by_user_id": null,
"created_at": "2026-07-17T00:00:00.000Z"
}
]
}

invited_by_user_id is null on that row: the founding owner was added by the act of creating the project.

2. Add your colleague​

Pick the role first. member reads and writes everything in the project — creating agents, minting project keys, connecting channels. admin also says who else is here and how the project is configured. Neither can delete the project, which stays with the owner.

naturali add-project-member \
--project-id "$PROJECT" \
--email ana@acme.com \
--role member
{
"id": "pmem_3ZjkQ2mN8pRtYv7X",
"project_id": "proj_V1StGXR8Z5jdHi6B",
"user_id": "user_3ZjkQ2mN8pRtYv7X",
"email": "ana@acme.com",
"name": null,
"status": "pending",
"role": "member",
"invited_by_user_id": "user_V1StGXR8Z5jdHi6B",
"created_at": "2026-07-17T00:05:00.000Z"
}

Keep the membership id — step 4 needs it:

export MEMBER=pmem_3ZjkQ2mN8pRtYv7X

status is pending because Ana has never signed in. If the address already had an account it would read active and she would have access immediately; either way the membership is recorded now, and naturali emails her a note saying who added her and to which project.

member is the role to give a colleague who is here to build. Keep admin for the people who should also decide who else is let in — an admin can do everything you can except delete the project, and cannot grant admin to anybody else, which is what keeps the two roles distinct.

warning

The address is the credential, so a typo grants access to whoever holds that address the first time they sign in. That is true of every emailed invitation. status: "pending" is what makes the mistake visible before anyone acts on it — check the list, and remove the member if the address is wrong (step 4).

3. Watch them arrive​

There is nothing to accept. Ana signs in the ordinary way — she requests an emailed code for her address, at the link in the invitation — and receiving that code is what proves the address is hers. No invitation token, and nothing to expire.

If you used an address you control, sign in with it now, then read the list again as yourself:

naturali list-project-members --project-id "$PROJECT"
{
"data": [
{ "email": "you@acme.com", "status": "active", "role": "owner" },
{ "email": "ana@acme.com", "status": "active", "role": "member" }
]
}

status flipped on its own. It is derived from whether the account has ever signed in, so nothing had to be accepted, swept or reconciled — and Ana was a member the whole time she was pending.

4. Change their role, or let them go​

Roles are not permanent. Promoting Ana to admin is one call, and the project owner is the only role that may make it:

naturali update-project-member \
--project-id "$PROJECT" \
--member-id "$MEMBER" \
--role admin
{
"id": "pmem_3ZjkQ2mN8pRtYv7X",
"email": "ana@acme.com",
"status": "active",
"role": "admin",
"invited_by_user_id": "user_V1StGXR8Z5jdHi6B",
"created_at": "2026-07-17T00:05:00.000Z"
}

Removing a member is the same shape, and it answers 204:

naturali remove-project-member \
--project-id "$PROJECT" \
--member-id "$MEMBER"

An admin may remove a member but not another admin, and anyone may remove themselves whatever their role — nobody has to ask to be let out. The owner's own row is the one thing no call here touches: it names who pays, and a project with no owner would be unreachable.

What's next​

  • Projects — the full role matrix and how membership relates to the billing owner.
  • API keys — a new colleague will want a project-scoped key of their own rather than sharing yours.
  • Approvals — now that somebody else can act in the project, a human in the loop can be a different human from the one paying.