Skip to main content

Allow cookies so we can count page views, see which pages and campaigns bring people here, and measure our ads on Meta, Google and LinkedIn. Cookie policy

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.