# Invite people and manage members

**You will:** Bring people into the organization and keep their access right, without the dashboard.

**You need:**
- invitations:read and invitations:write to invite
- members:manage to change a role, the cases someone sees, or remove them
- members:audit-read to read the audit log
- A key issued by an admin or the owner

Everything here follows the rules of the Team screen of the dashboard, capped by the current role of the person who issued the key (see [API keys](/guides/api-keys#a-key-acts-with-at-most-the-role-of-whoever-issued-it)):

| Issuer | May invite as | May change or remove |
| --- | --- | --- |
| Owner | `admin`, `member`, `viewer` | Admins, members and viewers |
| Admin | `member`, `viewer` | Members and viewers |

Nobody is invited or named owner by API, and the owner is never changed or removed by API.

The examples use this client:

```ts
import { Lelemon } from '@lelemon-ai/sdk';

const lelemon = new Lelemon({ apiKey: process.env.LELEMON_API_KEY });
```

## Invite a person

`POST /v2/invitations` sends an email with a link that lasts 7 days. The link is never returned by the API.

```bash title="cURL"
curl https://api.lelemon.cl/v2/invitations \
  -H "Authorization: Bearer $LELEMON_API_KEY" \
  --json '{ "email": "ana@acme.example", "role": "member" }'
```

```ts title="Node.js"
const invitation = await lelemon.invitations.create({
  email: 'ana@acme.example',
  role: 'member',
});
```

```bash title="CLI"
lelemon invitations create --email ana@acme.example --role member
```


The answer is the invitation (`inv_...`) with `status: "pending"` and `emailSent`. When the email could not be sent, the invitation exists anyway and `emailSent` is `false`: check the address and resend it. An email that already joined or has a pending invitation answers `409`.

## Follow, resend and cancel

- `GET /v2/invitations` lists every invitation, newest first, with `status` `pending`, `accepted`, `expired` or `cancelled`. Filter with `status`.
- `POST /v2/invitations/{id}/resend` renews the 7 days and sends a new link. The previous link stops working. It works on pending and expired invitations.
- `POST /v2/invitations/{id}/cancel` stops the link. The invitation stays listed as `cancelled`, and the same email can be invited again.

Resending or cancelling an accepted or cancelled invitation answers `409`. Both need a role that can give the role of the invitation.

```bash
lelemon invitations list --status pending
lelemon invitations resend inv_4Zq0x8bVw1TqS9mYp3RkLd
lelemon invitations cancel inv_4Zq0x8bVw1TqS9mYp3RkLd
```

## Change a member

`GET /v2/members` lists the people who already joined, with their `mbr_...` id, role and the cases they see. Then:

| Request | What it does |
| --- | --- |
| `PATCH /v2/members/{id}/role` | Sets `role` to `admin`, `member` or `viewer` |
| `PATCH /v2/members/{id}/case-scope` | Sets `caseScope`: `all`, `unassigned_and_own`, `assigned`, or `null` for the default of the organization |
| `DELETE /v2/members/{id}` | Removes the person. They lose access and leave their teams |

Owners and admins always see every case, so their `caseScope` cannot be set. `effectiveCaseScope` on each member tells you what applies right now. The [MCP connector](/tools/mcp) needs `all`. Setting a value the member already has writes nothing, so repeating a request is safe.

The keys a removed person issued keep only what a member may do, from the next call.

```ts title="Node.js"
await lelemon.members.updateRole('mbr_4Zq0x8bVw1TqS9mYp3RkLd', { role: 'viewer' });
await lelemon.members.updateCaseScope('mbr_4Zq0x8bVw1TqS9mYp3RkLd', { caseScope: 'assigned' });
await lelemon.members.remove('mbr_4Zq0x8bVw1TqS9mYp3RkLd');
```

```bash title="CLI"
lelemon members update-role mbr_4Zq0x8bVw1TqS9mYp3RkLd --role viewer
lelemon members update-case-scope mbr_4Zq0x8bVw1TqS9mYp3RkLd --case-scope assigned
lelemon members remove mbr_4Zq0x8bVw1TqS9mYp3RkLd
```


## Read who did what

`GET /v2/members/audit-log` lists every invitation created, resent and cancelled, every role and case scope change and every removal, newest first. Each entry has `action`, `actor` (the member, and the key when it came from one), `target`, and the role or scope before and after. Filter with `member` to see what was done to one person, their invitation included.

```bash
lelemon members audit-log list --member mbr_4Zq0x8bVw1TqS9mYp3RkLd
```

The audit log needs `members:audit-read`, which only an admin or the owner can give.

## Errors you will meet

| Error | Cause |
| --- | --- |
| `403 scope_missing` | The key lacks the scope, or the role of its issuer does not allow it |
| `403 forbidden` | The role of the issuer cannot give that role, or cannot touch that person |
| `409 conflict` | The email already joined or has a pending invitation, or the invitation was already accepted or cancelled |

## What's next

- [API keys](/guides/api-keys) for what a key can do depending on who issued it.
- [Command line](/tools/cli) for every command of the API.
