Teams
A team lets several people work in one Verid account. Members get a role that decides what they can do, API keys belong to the team, and every change anyone makes is written to an audit log you can filter and export. Team seats are included in every plan, with no per-seat charge.
What a team is
Every account has exactly one team, created when you sign up, and you are its owner. All of the account's data - monitors, SERP monitors, runs, deliveries, API keys, usage and run credits - belongs to the team, and the owner's plan applies to everyone in it: the monthly run pool, the history window, the trial and the billing are the owner's. There is no separate team plan to buy.
One person can belong to several teams - their own, plus any team that invited them - and switches between them in the dashboard. Ownership cannot be transferred and the owner cannot leave or be removed; to end a team, delete the owner account.
How many people a team can hold depends on the owner's plan:
| Plan | Team members included |
|---|---|
| Free trial | 2 |
| Lite | 2 |
| Starter | 3 |
| Pro | 10 |
| Scale | 25 |
The owner counts as one seat, and so does every pending invitation until it is accepted, revoked or expires. The SERP variants of a plan have the same seats as the base plan. When the team is full, inviting returns 429 TIER_LIMIT_EXCEEDED.
Roles
There are four roles. Each one includes everything the roles to its left can do.
| Can… | Viewer | Member | Admin | Owner |
|---|---|---|---|---|
| View monitors, SERP monitors, runs, deliveries, usage and settings | ✓ | ✓ | ✓ | ✓ |
| Create, edit, pause, resume and delete monitors and SERP monitors; trigger runs; rotate a monitor signing secret | - | ✓ | ✓ | ✓ |
| Replay deliveries | - | ✓ | ✓ | ✓ |
| Create, rotate and delete API keys | - | - | ✓ | ✓ |
| Change team settings and rename the team | - | - | ✓ | ✓ |
| Invite members, change roles, revoke invitations, remove members | - | - | ✓ | ✓ |
| Read and export the audit log | - | - | ✓ | ✓ |
| Manage billing (plans, cancellation, run credits) | - | - | - | ✓ |
Only admin, member and viewer can be given when inviting or changing a role; there is always exactly one owner. An admin can manage other admins, members and viewers but cannot change or remove the owner, and nobody can change their own role. Any member other than the owner can leave the team.
Invitations
Admins and the owner invite people by email from the Team page or with POST /api/v1/team/members/invite, choosing the role at the same time. The invitee receives an email with a link that:
- expires 7 days after it was sent (resending issues a fresh link and cancels the old one; each invitation can be resent up to 3 times an hour),
- must be accepted while signed in with the email address it was sent to - signing in with another address returns
409 INVITATION_INVALID, as does an expired, revoked or already used link, - counts towards the seat cap from the moment it is sent until it is accepted, revoked or expires.
Someone who does not have a Verid account yet signs up with the invited email first and then accepts. Accepting checks the cap again, adds the person with the invited role, switches their dashboard to the team and emails the person who invited them. Pending invitations can be revoked at any time.
Switching teams
The dashboard always shows one team at a time: the active team, stored on your session. Use the team switcher in the sidebar, or GET /api/v1/team/list and POST /api/v1/team/switch, to move between the teams you belong to. The team marked is_personal is the one you own. Switching only changes what you see; it never moves data between teams.
API keys in a team
API keys are team-scoped: a key created by any admin or the owner works on the whole team's data, and the account that created it is recorded on the key and in the audit log, so you can tell keys apart.
A request made with an API key behaves like an admin on monitors, SERP monitors, runs, deliveries and usage, limited by the key's scope: a key with the read scope only can list and read, a key with write can also create, edit, trigger runs and replay deliveries. An API key can never:
- create, rotate or delete API keys, or change team settings,
- rename the team, invite, remove or change members, or revoke invitations,
- export the audit log as CSV, or do anything with billing.
Among the team endpoints, an API key may call only GET /api/v1/team, GET /api/v1/team/members and GET /api/v1/team/audit. Everything else under /api/v1/team requires a signed-in user and answers an API key with 403 FORBIDDEN.
Audit log
Every change made through the dashboard or the API is recorded with who did it (a user, an API key, or the system), what was affected, the IP address the request came from and a small JSON metadata object with the specifics - for example the fields that changed on a monitor, or the previous and new role of a member. Secrets are never written to the log. Reads are not recorded.
The recorded actions are:
| Group | Actions |
|---|---|
| Monitors | monitor.created, monitor.updated, monitor.deleted, monitor.paused, monitor.resumed, monitor.run_triggered, monitor.secret_rotated, monitor.bulk_action |
| SERP monitors | serp_monitor.created, serp_monitor.updated, serp_monitor.deleted, serp_monitor.paused, serp_monitor.resumed, serp_monitor.run_triggered, serp_monitor.secret_rotated |
| Deliveries | delivery.replayed |
| API keys | api_key.created, api_key.rotated, api_key.deleted |
| Settings | settings.proxy_toggled |
| Team | team.renamed, team.member_invited, team.invitation_resent, team.invitation_revoked, team.member_joined, team.member_role_changed, team.member_removed, team.member_left |
| Billing | billing.checkout_started, billing.plan_changed, billing.cancelled, billing.resumed, billing.credits_purchased |
Admins and the owner read the log on the Team page or with GET /api/v1/team/audit, filtering by action, actor_user_id, target_type and a from / to time range. The same filters apply to GET /api/v1/team/audit/export, which returns a CSV of up to 50,000 rows with these columns:
| Column | Meaning |
|---|---|
time | When the event happened (ISO 8601) |
actor | The user email, the API key name, or System |
actor_type | user, api_key or system |
action | One of the action names above |
target_type | monitor, serp_monitor, delivery, api_key, settings, team, member, invitation or subscription |
target_id | ID of the affected record, if any |
target_label | Readable name: the monitor name, the key prefix, the member email |
ip_address | IP the request came from, when known |
metadata | Small JSON object with the details, for example the previous and new role |
Audit events are kept for the same window as your run history, which depends on the owner's plan. Older events are deleted by a daily cleanup.
| Plan | Audit log retention |
|---|---|
| Free trial | 14 days |
| Lite | 90 days |
| Starter | 180 days |
| Pro | 365 days |
| Scale | 730 days |
Permission errors
When a role or an API key is not allowed to do something, the API answers with HTTP 403 and the standard error envelope. The message names the action that was refused.
HTTP/1.1 403 Forbidden
{
"error": {
"code": "FORBIDDEN",
"message": "Viewers can't do this (monitors:write). Ask a team admin or the owner."
}
}Dashboard behaviour
The dashboard hides or disables the controls a role cannot use, so a viewer never sees a Create monitor button. The API check is the source of truth; the UI only mirrors it.API reference
All team endpoints live under /api/v1/team and use the same authentication, pagination and error format as the rest of the API. Unless noted, they require a signed-in user; see API keys in a team for the three that accept an API key.
Team
GET /api/v1/team - the active team. Any role, and API keys.
{
"id": "uuid",
"name": "Acme",
"owner_user_id": "uuid",
"owner_email": "owner@acme.com",
"role": "admin",
"tier": "pro",
"member_count": 4,
"pending_invites": 1,
"max_members": 10,
"created_at": "2026-10-10T09:00:00.000Z"
}role is the caller's role in this team (api_key when called with a key), tier is the owner's plan and max_members the seats it includes. member_count plus pending_invites is what counts against max_members.
PATCH /api/v1/team with { "name": "Acme" } (1 to 80 characters) renames the team. Admin or owner. Returns the same shape as GET.
GET /api/v1/team/list - every team the signed-in user belongs to, for the switcher.
{
"data": [
{
"id": "uuid",
"name": "Acme",
"role": "admin",
"owner_email": "owner@acme.com",
"is_personal": false,
"active": true
}
]
}POST /api/v1/team/switch with { "team_id": "uuid" } makes that team the active one for the session. Returns { "team_id": "uuid" }; 404 if you are not a member.
Members
GET /api/v1/team/members - current members and pending invitations. Admin or owner, and API keys.
{
"members": [
{
"user_id": "uuid",
"email": "owner@acme.com",
"name": "Sam Owner",
"role": "owner",
"joined_at": "2026-10-01T09:00:00.000Z"
}
],
"invitations": [
{
"id": "uuid",
"email": "new@acme.com",
"role": "member",
"invited_by_email": "owner@acme.com",
"expires_at": "2026-10-17T09:00:00.000Z",
"created_at": "2026-10-10T09:00:00.000Z"
}
]
}POST /api/v1/team/members/invite - send an invitation. Admin or owner. role is admin, member or viewer and defaults to member.
{ "email": "new@acme.com", "role": "member" }Returns 201 with the invitation. email_sent is false if the email could not be delivered; the invitation still exists and can be resent. 409 if the address is already a member or already has a pending invitation, 429 TIER_LIMIT_EXCEEDED when the team is full.
{
"id": "uuid",
"email": "new@acme.com",
"role": "member",
"invited_by_email": "owner@acme.com",
"expires_at": "2026-10-17T09:00:00.000Z",
"created_at": "2026-10-10T09:00:00.000Z",
"email_sent": true
}PATCH /api/v1/team/members/{userId} with { "role": "admin" | "member" | "viewer" } changes a member's role. Admin or owner; the owner's role and your own role cannot be changed. Returns the member (user_id, email, name, role, joined_at).
DELETE /api/v1/team/members/{userId} removes a member (admin or owner), or, with your own user id, leaves the team (any role except owner). Returns { "left": true | false }. The owner cannot be removed.
Invitations
GET /api/v1/team/invitation?token=… - public, no authentication. Previews the invitation behind a link so the accept page can show it. Rate limited per IP address.
{
"state": "open",
"team_name": "Acme",
"email": "new@acme.com",
"role": "member",
"invited_by": "Sam Owner",
"expires_at": "2026-10-17T09:00:00.000Z"
}state is open, expired, revoked or accepted.
POST /api/v1/team/accept with { "token": "…" } - accept an invitation as the signed-in user. Returns { "team_id", "team_name", "role" } and switches the session to that team. 409 INVITATION_INVALID when the link is expired, revoked, already used, or was sent to a different email address.
POST /api/v1/team/invitations/{id}/resend - issue a new link for a pending invitation and email it again; the previous link stops working. Admin or owner. Returns the new invitation with email_sent, like invite.
DELETE /api/v1/team/invitations/{id} - revoke a pending invitation. Admin or owner. Returns 204; 409 if it was already accepted or revoked.
Audit log
GET /api/v1/team/audit - paginated events, newest first. Admin or owner, and API keys. Query parameters, all optional:
| Parameter | Meaning |
|---|---|
page | Page number, default 1 |
limit | Rows per page, default 50, max 100 |
action | One action name, for example monitor.deleted |
actor_user_id | Only events by this user |
target_type | One target type, for example api_key |
from, to | ISO 8601 timestamps bounding created_at |
{
"data": [
{
"id": "uuid",
"action": "team.member_role_changed",
"target_type": "member",
"target_id": "uuid",
"target_label": "dev@acme.com",
"actor": { "type": "user", "id": "uuid", "label": "owner@acme.com" },
"metadata": { "from": "viewer", "to": "member" },
"ip_address": "203.0.113.4",
"created_at": "2026-10-10T09:12:00.000Z"
}
],
"pagination": { "page": 1, "limit": 50, "total": 1, "totalPages": 1, "hasMore": false },
"actions": ["team.member_role_changed", "monitor.created"]
}actor.type is user, api_key or system; actor.id is the user id or key id, and actor.label the email or key name. actions lists every action name that appears in this team's log, for building a filter menu.
GET /api/v1/team/audit/export - the same filters, returned as a CSV attachment (text/csv) of up to 50,000 rows. Admin or owner, signed-in only.