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 isolationThat 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/agentslists every tenant's agents. Its filter is nothing but arequire_tenantcall in atry/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 becometenant_forbidden(which this platform never raises in either mode). It just works — sessions, calls and agents alike, since they all route through the samerequire_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_authraises a401with{"detail": "authentication required"}when no principal was resolved (backend/app/security/deps.py:28-29).- Before a request even reaches a
/v1handler,AuthMiddlewarechecks for a resolved principal itself and, in enforce mode, short-circuits with the same401and body —{"detail": "authentication required"}— with no error envelope: notype,code,request_idordoc_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. Seeunauthenticated, which documents this bare-body case explicitly — a client should treat that response the same as the envelopedunauthenticatederror the/v1handlers themselves produce when a request gets further before failing. require_role,require_scopeandrequire_platform_adminadd role- and scope-checks on top ofcurrent_auth, and — likecurrent_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.