DocsCore conceptsTeams

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:

PlanTeam members included
Free trial2
Lite2
Starter3
Pro10
Scale25

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…ViewerMemberAdminOwner
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:

GroupActions
Monitorsmonitor.created, monitor.updated, monitor.deleted, monitor.paused, monitor.resumed, monitor.run_triggered, monitor.secret_rotated, monitor.bulk_action
SERP monitorsserp_monitor.created, serp_monitor.updated, serp_monitor.deleted, serp_monitor.paused, serp_monitor.resumed, serp_monitor.run_triggered, serp_monitor.secret_rotated
Deliveriesdelivery.replayed
API keysapi_key.created, api_key.rotated, api_key.deleted
Settingssettings.proxy_toggled
Teamteam.renamed, team.member_invited, team.invitation_resent, team.invitation_revoked, team.member_joined, team.member_role_changed, team.member_removed, team.member_left
Billingbilling.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:

ColumnMeaning
timeWhen the event happened (ISO 8601)
actorThe user email, the API key name, or System
actor_typeuser, api_key or system
actionOne of the action names above
target_typemonitor, serp_monitor, delivery, api_key, settings, team, member, invitation or subscription
target_idID of the affected record, if any
target_labelReadable name: the monitor name, the key prefix, the member email
ip_addressIP the request came from, when known
metadataSmall 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.

PlanAudit log retention
Free trial14 days
Lite90 days
Starter180 days
Pro365 days
Scale730 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.

text
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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{ "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.

json
{
  "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.

json
{
  "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:

ParameterMeaning
pagePage number, default 1
limitRows per page, default 50, max 100
actionOne action name, for example monitor.deleted
actor_user_idOnly events by this user
target_typeOne target type, for example api_key
from, toISO 8601 timestamps bounding created_at
json
{
  "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.