How to Proxy API Requests for Realtime AI Avatars in Python So Secrets Never Reach the Browser

Python proxy pattern for realtime AI avatars: keep API keys server-side, create sessions safely, and prevent browser secret leaks.
Introduction
The basic problem is simple: if your browser can talk directly to a realtime avatar API, then your API key can leak. It can leak through DevTools, source maps, browser extensions, XSS, copied network requests, or just by someone viewing the page source. For a realtime avatar product, that is especially risky because the browser often needs to create sessions, negotiate media, and pass per-session instructions or voice settings.
If you are building with Protoface, the safe pattern is to keep all privileged API calls on your server and let the browser talk only to your backend. By the end of this post, you should be able to design a small proxy layer in Python that:
keeps API keys out of the browser entirely,
creates or manages avatar sessions on behalf of the client,
passes through only the minimum data the browser actually needs, and
supports realtime avatar workflows without turning your frontend into a security liability.
Why direct browser-to-API calls are the wrong default
Any request that leaves the browser should be treated as public. That is true even if you use HTTPS. TLS protects the transport, not the secrecy of the token once it is present in JavaScript. For API-key-authenticated services, the browser should never receive the long-lived secret used to authorize management operations.
With realtime avatars, the temptation is to call the API directly from the frontend because it feels simpler: “create a session, get back connection info, start streaming.” That simplicity is deceptive. The moment your frontend can create sessions directly, it can also be used to burn your quota, enumerate resources, or mint sessions with arbitrary settings. If the request includes a bearer token, that token is effectively public.
The safer model is familiar from payment or messaging systems:
The browser asks your backend for a narrow, purpose-built action.
Your backend authenticates the user, applies business rules, and makes the privileged API call.
Your backend returns only the data the browser needs to proceed.
That proxy layer does not have to be large. For many applications, it is a single route that forwards a few parameters to the avatar API, validates them, and returns sanitized results.
Designing a minimal proxy in Python
A good proxy endpoint should be boring. It should do four things well:
Authenticate the caller using your app session, cookie, JWT, or other frontend auth.
Validate the request parameters against an allowlist.
Attach the server-side API key when calling the avatar service.
Return only the response fields needed by the browser.
Do not forward arbitrary JSON from the browser to the upstream API. If the browser can set every field, then your proxy is just a thin disguise for direct access. Restrict the shape of the request to what your product actually supports.
Here is a small FastAPI-style example using plain httpx. The exact request and response fields depend on the endpoint you are calling, so treat this as a pattern rather than a literal contract:
Two things matter here. First, the bearer token is only present server-side. Second, the response is intentionally narrow. If the upstream returns extra metadata, don’t casually send it to the browser unless you have a reason.
What to proxy, and what to keep server-side
Not every request belongs in the browser. For realtime avatar systems, it helps to split operations into three buckets.
Keep server-side:
creating avatars, session records, and API keys,
setting quality tier or billing-sensitive options,
writing custom instructions or per-tenant defaults,
applying allowlists, quotas, and abuse controls.
Can be browser-initiated through your proxy:
start a session for the authenticated user,
choose among preapproved avatars or voices,
send message text or event payloads for a live interaction,
request a short-lived connection artifact if your backend generates one.
Usually should not be browser-controlled:
API keys,
tenant-wide avatars or instructions,
rate limit policy,
upstream webhook secrets, if you use them.
If you need user-specific customization, pass only a high-level selector from the frontend. For example, let the client choose “sales assistant” or “support agent,” then translate that to a server-owned avatar ID and approved instruction template.
Handle realtime details carefully
Realtime avatars usually sit on top of media transport such as WebRTC or a similar low-latency channel. The browser may need to establish a connection to an avatar session, but that does not imply the browser should know anything about your upstream credentials. Think of the browser as a participant in a session, not as an administrator of the avatar platform.
A few practical gotchas show up repeatedly:
Short-lived session artifacts: if the browser needs a token or connection URL, make it short-lived and scoped to one session.
Idempotency: users click twice. Your proxy should avoid creating duplicate sessions for a single action unless that is intentional.
Rate limits: enforce them at your boundary, not only upstream. Per-user and per-IP controls reduce accidental and malicious churn.
Instruction injection: never accept raw system-style instructions from untrusted clients. Treat them like code, not like text.
The last point is worth emphasizing. In conversational systems, prompt-like fields can influence behavior just as much as a config flag. If a frontend can send arbitrary instructions directly to the avatar service, you have handed users a control plane you probably did not intend to expose.
Example: proxying a curl request through your backend
From the browser’s perspective, the request should go to your domain, not to the avatar API. That keeps your secret out of the network tab and lets you enforce application rules centrally.
Your backend then makes the upstream call with the server-side bearer token. If you need to debug the upstream request, log it carefully and redact secrets. Do not print the raw Authorization header, and do not serialize full request bodies if they may contain user data or instructions.
How Protoface fits this pattern
Protoface exposes a REST API for creating and managing avatars and realtime sessions, and that is the surface you proxy from Python. The point is not to expose the API directly to the browser; the point is to let your backend safely mediate access while keeping the browser experience simple. The public docs at docs.protoface.com cover the exact request shapes, and the Python SDK can reduce some of the HTTP plumbing if you prefer a typed client over raw httpx.
A minimal SDK-based backend route looks conceptually like this:
If you are integrating an avatar into a voice agent built on LiveKit, the same principle still applies. The plugin adds the avatar into the agent pipeline, but your app should still keep any privileged API usage on the server. The browser should only receive whatever connection details are required for the session it is already authorized to use.
If you prefer to start from a reference implementation, the GitHub quickstarts linked from the Protoface docs are useful for seeing where the server boundary belongs in a real app.
Implementation checklist
If you are retrofitting an existing frontend, use this checklist to avoid accidental exposure:
Move every API-key-authenticated call out of the browser.
Make your proxy accept a small, typed request schema.
Map frontend choices to server-owned avatar IDs, voices, and instruction templates.
Return only short-lived, session-specific data to the client.
Enforce auth, rate limits, and tenant boundaries before the upstream call.
Log enough to debug, but redact secrets and user content where appropriate.
Conclusion
The safest way to integrate realtime avatars is not to hide secrets in the frontend; it is to never send them there in the first place. Put a thin Python proxy in front of the avatar API, validate input aggressively, and keep session creation and management on the server. That gives you a clean boundary for auth, rate limiting, billing control, and future changes to your avatar workflow.
If you are implementing this now, start with the REST API or the Python SDK, then compare your backend route against the examples in the docs. If your use case is a voice agent, the LiveKit plugin and its examples are the right place to see how the avatar layer plugs into a realtime pipeline without exposing credentials to the browser.
