Skip to main content
VARTA

How does authentication work on the VARTA API?

Read this before you point /v1 at anything other than your own machine. The default behaviour is more permissive than an integrator coming from a typical SaaS API is likely to assume.

The default mode does not reject unauthenticated requests

Every /v1 handler resolves the calling identity through current_auth (backend/app/security/deps.py:24-30). What that dependency does depends on one setting: settings.auth_mode, which defaults to "audit" (backend/app/core/config.py:26).

In audit mode, current_auth does not reject a request with no credential — it returns a permissive fallback context (_AUDIT_FALLBACK, an admin-role context on the default tenant) instead of raising (backend/app/security/deps.py:17, :24-30). The auth middleware that runs in front of the handlers behaves the same way: with no principal resolved from the request, audit mode logs a would_reject line and lets the request continue (backend/app/security/middleware.py:48-52).

Put plainly: on a default self-hosted VARTA instance, /v1 does not reject unauthenticated requests. POST /v1/calls — the endpoint that places a real telephone call — will run for a caller that sends no Authorization header at all. This is a deliberate operational default (the comment at deps.py:12-16 explains it exists so an internal console that doesn't yet send tokens isn't broken by a hard 401 the moment a route is wired), not a security posture to rely on. If you are exposing your instance beyond your own network, set auth_mode to "enforce" before you do it. An integrator who assumes authentication is on by default, and ships a /v1 deployment reachable from the internet without checking this setting, has shipped an unauthenticated call-placing API.

Audit mode also disables tenant isolation

auth_mode does not only gate authentication. The same setting gates object-level isolation — which tenant's resources a caller can reach — and that is the half most integrators miss, because every API page on this site otherwise describes isolation as if it were unconditional.

Ownership is checked in one place, require_tenant (backend/app/security/ownership.py:43-59). Its first act is to opt out:

if not _enforcing():
    return resource                     # audit: observe, don't enforce isolation

That early return is backend/app/security/ownership.py:51-52, and _enforcing() is just settings.auth_mode == "enforce" (backend/app/security/ownership.py:17-22). So on a default instance the tenant comparison below that line never runs, and the resource is returned to whoever asked for it.

The consequences are concrete, not theoretical:

  • GET /v1/agents lists every tenant's agents. Its filter is nothing but a require_tenant call in a try/except — an agent is included whenever that check doesn't raise (backend/app/api/v1/agents.py:90-102). In audit mode it never raises, so the "agents visible to your tenant" list is in fact the whole instance.
  • A well-formed id belonging to another tenant resolves normally. It does not become not_found, and it does not become tenant_forbidden (which this platform never raises in either mode). It just works — sessions, calls and agents alike, since they all route through the same require_tenant.

Two things follow. First: the isolation described on the API reference pages is enforce-mode behaviour, and each of those pages now says so. Second, and more sharply — if you are reselling or multi-tenanting a VARTA instance, audit mode is not a softer security posture, it is the absence of one. Tenant separation is the property your customers are buying; it is off until you turn auth_mode to "enforce". Verify the setting on the instance itself rather than assuming a deployment inherited it.

What enforce mode does

With settings.auth_mode == "enforce":

  • current_auth raises a 401 with {"detail": "authentication required"} when no principal was resolved (backend/app/security/deps.py:28-29).
  • Before a request even reaches a /v1 handler, AuthMiddleware checks for a resolved principal itself and, in enforce mode, short-circuits with the same 401 and body — {"detail": "authentication required"} — with no error envelope: no type, code, request_id or doc_url (backend/app/security/middleware.py:48-50). That is the form you will actually see for most missing-credential requests, because the middleware runs first. See unauthenticated, which documents this bare-body case explicitly — a client should treat that response the same as the enveloped unauthenticated error the /v1 handlers themselves produce when a request gets further before failing.
  • require_role, require_scope and require_platform_admin add role- and scope-checks on top of current_auth, and — like current_auth — only actually enforce them in enforce mode (backend/app/security/deps.py:33-61).

The credential the code reads

Send the credential as a standard bearer header:

Authorization: Bearer <token>

The middleware reads the Authorization header, and only accepts the Bearer scheme — any other scheme (or none) resolves to no principal (backend/app/security/principal.py:21-30). The token itself is a signed, short-lived JWT minted by the instance's own TokenService (backend/app/security/tokens.py) with claim "typ": "service" — service tokens are the only Bearer-token principal type accepted on /v1; a channel token (the type used for the realtime WebSocket path) is deliberately rejected here even if presented the same way (backend/app/security/principal.py:16-30).

This documentation could not find a /v1 or studio HTTP endpoint that mints a service token for you — every mint call in the codebase is either test code or an internal flow that issues a different token type (a session cookie for the studio's own login, a channel token for a live call's WebSocket). Token issuance for external /v1 callers is an operator concern on the instance you're talking to, not a self-serve flow this API exposes today. If you're integrating against someone else's VARTA instance, ask its operator for a token rather than looking for a signup endpoint; if you operate the instance yourself, your own deployment tooling is what mints one.

What to send

curl "$VARTA_BASE_URL/agents" \
  -H "Authorization: Bearer $VARTA_API_KEY"

Every example on this site uses $VARTA_API_KEY as the environment variable name for this token, regardless of whether the instance you're calling actually enforces it yet.