# Invites and team

Tenants are multi-user. Invite teammates from the **Team** page; they accept by following the link in their email and creating a password.

## Send an invite

1. Sidebar → **Team**.
2. Click **Invite member**.
3. Enter their email + select a role (`member`, `admin`, or `owner` — see [`10-setup-account-tenant.md`](./10-setup-account-tenant.md#roles-inside-a-tenant)).
4. Click **Send invite**.

The invitee receives an email containing a single-use invite link. The link is valid for 7 days.

![Invite member dialog](screenshots/portal-team-invite.png)

> Free tier is capped at 3 users per tenant. If you've already used your three slots the **Invite member** button is disabled with a tooltip pointing at the upgrade page.

## Pending invites

The Team page lists pending invites separately from active members. From here you can:

- **Copy invite link** — useful when the invitee says they didn't get the email
- **Resend email** — re-issues the same token (the original link still works)
- **Revoke** — kills the link; the invitee gets `invite-revoked` if they try to use it

![Team page with pending-invite row and actions](screenshots/portal-team-pending-invite.png)

## Accepting an invite (what the invitee sees)

The link drops them at `https://activitylog.com/portal/accept-invite?token=…`. They:

1. See a confirmation of the tenant they're joining and the role you've assigned.
2. Set a password (≥ 12 chars).
3. Land on the Dashboard already signed in.

![Invite acceptance screen](screenshots/portal-invite-accept.png)

If their email matches an existing account, they're prompted to sign in instead — adding the new tenant membership to the existing account. They can switch between tenants via the top-right tenant picker.

### Errors the invitee might see

| Slug | Meaning | Fix |
|---|---|---|
| `invite-not-found` | Token doesn't exist | The admin probably regenerated; ask for a fresh link |
| `invite-expired` | Older than 7 days | Ask for a fresh link |
| `invite-revoked` | Admin clicked Revoke | Ask for a fresh link |
| `invite-already-accepted` | Token was already redeemed | The user is already in — they should sign in normally |

## Removing a user

**Team → click the user → Remove**. They lose access immediately; their browser session is invalidated on the next API call. Their previously-ingested data is unaffected.

If the user is an **owner**, the system blocks removal until at least one other owner exists. Promote someone first.

![Remove member confirmation](screenshots/portal-team-remove-confirm.png)

## Changing roles

**Team → click the user → Edit role.** Adding or removing the `admin` bit takes effect on their next page load.

> The TOTP requirement and per-action re-prompt only apply to **Fyin staff** in the operator portal (`/portal/admin/`). Customer tenant admins have the standard customer session security — see [`40-totp-setup.md`](./40-totp-setup.md) for what's available customer-side.

## Audit trail

Every invite-issued, invite-accepted, invite-revoked, role-changed, and user-removed event is recorded in the tenant audit trail and surfaces under **Audit** in the admin portal (for Fyin staff) or via `GET /admin/audit` if you're querying the API directly. Customer-side audit-trail visibility is on the roadmap; today operators see it via support.

## Best practices

- **One admin per environment, at least.** If only one human can administer the tenant, lockout becomes a support call.
- **Auditors / read-only contractors get `member`.** They can query and view dashboards without risk of accidentally revoking a production token.
- **Revoke before you offboard.** When someone leaves the company, remove their user before the deprovisioning happens — otherwise their email might bounce a few attempts before lockout.

## What's next

| Goal | Doc |
|---|---|
| Enable two-factor on your account | [`40-totp-setup.md`](./40-totp-setup.md) |
| Understand operator (Fyin-staff) admin features | [`80-admin-portal.md`](./80-admin-portal.md) |
| See what someone did | (audit-export — coming soon) |

## Troubleshooting

**The invitee never got the email.**
Check spam first; we send via Microsoft Graph and most enterprise filters pass it cleanly, but transactional email is never guaranteed. From the Team page click **Copy invite link** and send it directly.

**`invite-already-accepted` but the user says they didn't accept.**
Someone with access to their email might have. Check the **Audit** entry for the invite — it logs the accepting account's IP. If suspicious, immediately revoke their portal session (Team → user → **Force sign-out**) and have them reset their password.

**I want to remove the last owner.**
You can't from the customer portal. Promote another member to owner first, or contact Fyin support for an operator-side change.
