Guides

Team and security

Roles, invitations, two-factor login, sessions, the security log, email verification, password rules and API keys.

A workspace is one account. The people who log in to it are members, each with a role.

In the dashboard: Settings → Account → Team and Security, and Developers → API Keys.

Roles

Roles are ordered; each can do everything the ones before it can.

RoleCan do
viewerRead agents, calls, batches, numbers, knowledge bases and settings, plus the balance, plans and prices. Manage their own password, two-factor login and sessions. Cannot read the ledger, invoices, message log, failed jobs or security log
memberAlso create and change agents, knowledge bases, batches, workflows and campaigns, and place calls
adminAlso phone numbers, SIP trunks, provider keys, custom models, API keys, compliance settings, the do-not-call list, account settings, sub-accounts, webhook test and resend, failed jobs, the message log, billing records, the team and the security log
ownerAlso billing changes (plan, top-ups, tax details, alerts), transferring ownership and deleting the workspace

An API key acts as admin. It cannot top up the wallet or change the plan.

A request the role does not allow is 403:

json
{"error": 403, "message": "Your role (viewer) can't change phone numbers. It needs admin access; ask an owner or admin of the workspace."}

GET /auth/me returns your member record and role. GET /team/permissions returns the rule table the server enforces.

Invite people

bash
curl -X POST https://api.sevennadi.si/team/invites \
  -H "Authorization: Bearer vs-0123456789abcdef0123456789abcdef" \
  -H "Content-Type: application/json" \
  -d '{"email": "ravi@example.test", "role": "member"}'

The response (201) describes the invitation, with expires_at and who sent it. The person gets an email with a link valid for 7 days, and finishes with a name and password.

SituationAnswer
Already a member409 This person is already a member of the workspace
Already invited409 This person already has a pending invitation. Resend it instead.
Address has a login in another workspace409 This email address already has a login in another workspace
Admin inviting an owner403 Only an owner can add, change or remove an owner.

GET /team/invites lists pending invitations, POST /team/invites/{invite_id}/resend sends the email again, and DELETE /team/invites/{invite_id} withdraws one.

Manage members

  • GET /team/members lists members.
  • PATCH /team/members/{member_id} with {"role": "admin"} or {"disabled": true}.
  • DELETE /team/members/{member_id} removes someone.

Guard rails: nobody changes their own role, disables their own login or removes themself. Only an owner touches an owner. A workspace always keeps at least one active owner.

Owners only: POST /team/transfer-ownership with {"member_id", "password"}, and DELETE /team/workspace with {"password", "confirm": "<workspace name>"}, which soft-deletes the workspace.

Passwords

  • At least 8 characters, at most 200.
  • Not your email address.
  • Not one of a short list of very common passwords.
  • Stored as scrypt hashes.

POST /auth/change-password with {"current_password", "new_password"} keeps your current session and ends the others. POST /auth/forgot-password with {"email"} gives the same answer whether or not the address exists; its link works once, for one hour. POST /auth/reset-password with {"token", "password"} ends every session of that login.

Failed logins are throttled: 10 per 15 minutes per email and address, then 429 Too many login attempts. Try again in 15 minutes.

Email verification

Signing up sends a verification link, valid 24 hours and usable once. POST /auth/verify-email with {"token"} confirms it; POST /auth/resend-verification sends another (three an hour).

Unverified logins work. If the operator requires verified email, these answer 403 until the address is confirmed: placing calls, creating and scheduling batches, starting or resuming campaigns, running workflows, and creating API keys.

Two-factor login

Two-factor login uses time-based codes from an authenticator app (TOTP).

  1. POST /auth/2fa/setup returns {"secret", "otpauth_uri"}. Show the URI as a QR code.
  2. POST /auth/2fa/enable with {"code"} turns it on and returns 8 single-use recovery_codes. Store them.
  3. From then on POST /auth/login answers {"two_factor_required": true, "challenge": "..."}. Finish with POST /auth/login/2fa and {"challenge", "code"} (or "recovery_code") within 5 minutes.

Codes are accepted one step either side of the current time and never twice. Five wrong codes lock the second step for 15 minutes. POST /auth/2fa/disable needs your password and a code or recovery code. These endpoints need a dashboard login; an API key gets 403 This needs a dashboard login, not an API key.

Sessions

A login returns a session token (vs- followed by 32 hex characters) that works as a Bearer token for 7 days.

  • GET /auth/sessions lists your sessions.
  • DELETE /auth/sessions/{session_id} ends one.
  • POST /auth/sessions/revoke-all with {"keep_current": true} ends the rest.

Security log

bash
curl "https://api.sevennadi.si/team/audit?event=login_failed&page_size=20" \
  -H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"
json
{"page_number": 1, "page_size": 20, "total": 1, "has_more": false, "data": [
  {"id": 311, "event": "login_failed", "actor": "ravi@example.test", "member_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
   "detail": {"reason": "wrong password"}, "ip": "203.0.113.7", "user_agent": "Mozilla/5.0", "created_at": "2026-10-13T11:20:00Z"}
]}

It records logins and failed logins, password and two-factor changes, invitations, role changes, and API keys created and revoked. page_size is at most 100.

API keys

bash
curl -X POST https://api.sevennadi.si/auth/api-keys \
  -H "Authorization: Bearer vs-0123456789abcdef0123456789abcdef" \
  -H "Content-Type: application/json" \
  -d '{"name": "Production server"}'

Response, 201:

json
{"id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e", "name": "Production server", "prefix": "vk-01234",
 "created_at": "2026-10-13T11:30:00Z", "last_used_at": null, "key": "vk-0123456789abcdef0123456789abcdef"}

The full key is shown only in this response. The platform stores a hash, so a lost key cannot be recovered: revoke it and create another. GET /auth/api-keys lists keys by prefix and last_used_at. DELETE /auth/api-keys/{key_id} revokes one.

More on tokens in Authentication.

Limitations

  • No external security audit or penetration test has been done.
  • No single sign-on, no IP allow-lists for API keys, and no per-key scopes: every API key is an admin.
  • An authenticator app is the only second factor, and recovery codes the only fallback.
  • Customer data (transcripts, recordings, contact lists) is not encrypted at rest by the platform.
  • By default the platform stores emails in a development outbox instead of sending them. The operator must configure SMTP for real invitation and reset emails.