Data Residency
Radioso’s own backend, database, and documents run wherever you deploy them. A handful of outside services sit behind that: a model provider for chat and embeddings, a voice vendor for calls, an email provider for outbound and inbound mail, product analytics, Slack, and Stripe. Each one decides where it processes the data you send it, and that’s a separate question from where Radioso itself runs. This page lists each one, what it handles, and the setting — if there is one — that controls its region, then where each stack’s own logs are stored.
OpenAI
The native openai provider backs chat, embeddings, and rerank. By default, platform-held OpenAI traffic goes to OpenAI’s global API. Set OPENAI_DATA_RESIDENCY=eu or OPENAI_DATA_RESIDENCY=us on the backend, document worker, and crawler worker to send it to OpenAI’s EU or US data-residency endpoint instead.
Getting OpenAI itself to process requests in-region takes its own setup, separate from this flag:
- Apply for OpenAI’s abuse-monitoring controls and sign its Modified Retention amendment. Regional processing requires both.
- Create a project with its region set to Europe (or United States) and issue an API key from it. An existing project can’t change region later. A key from a project with Global geography reaches chat and Responses on the regional endpoints once your organization is eligible. The regional embeddings endpoint accepts only keys from a project created with a region.
- Confirm every model you use, including the embedding model, supports regional processing in that region.
- Set
OPENAI_DATA_RESIDENCYandOPENAI_API_KEY(the regional project’s key) together, then redeploy. Each runtime — the backend, the document worker, and the crawler worker — checks the key against the regional endpoint once at startup, before it serves traffic, for every platform OpenAI model it’s configured to call (embeddings, chat, rewrite, and rerank), and refuses to start if the endpoint rejects any of them. An OpenAI outage during that check only logs a warning and lets startup continue, so a transient OpenAI problem never blocks an otherwise-good deploy.
A workspace’s own OpenAI key (Settings → Providers, bring-your-own-key) carries its own residency choice, picked next to the key: Global (the default), US, or EU. It’s independent of OPENAI_DATA_RESIDENCY — a workspace can bring a key from a Europe-region project while the platform key stays global, or the other way around. Match the choice to whichever region the key’s own OpenAI project was created with; the workspace operator authors it, since Radioso has no way to tell a regional key from a global one by looking at it. Changing the choice later needs no new key — pick the new region next to the stored one and it takes effect on the next call.
Azure OpenAI
Azure OpenAI runs the same models in an Azure region, under your own Azure subscription, with a key Azure issues rather than OpenAI. It’s a second route to the same regional-processing outcome as the section above, without OpenAI’s own abuse-monitoring approval step.
- In Azure AI Foundry, deploy each model your platform key uses — chat, rewrite, rerank, and the embedding model — as a Data Zone Standard deployment in an EU (or other) region. Give each deployment the same name as the model it serves (for example, deploy
gpt-5.4-miniwith the deployment namegpt-5.4-mini): the nativeopenaiclient sends the model name as the request’smodelfield, and Azure resolves that to a deployment by name. Data Zone Standard quota is separate from Global Standard quota, so a model your subscription already has on Global Standard still needs its own Data Zone Standard deployment. The host accepts*.openai.azure.com,*.services.ai.azure.com, or*.cognitiveservices.azure.comresource endpoints. - Set
AZURE_OPENAI_ENDPOINTto the resource’s endpoint (shown in the Azure portal as something likehttps://<resource>.openai.azure.com),OPENAI_API_KEYto that resource’s key, and leaveOPENAI_DATA_RESIDENCYat its default,global. SettingAZURE_OPENAI_ENDPOINTtogether with a non-globalOPENAI_DATA_RESIDENCYis a configuration error — the two routes are mutually exclusive — and the backend refuses to start until one of them is unset. - Redeploy the backend, document worker, and crawler worker with the new settings. Each one checks every configured platform-OpenAI model — embeddings, chat, rewrite, and rerank — against the Azure endpoint once at startup, before it serves traffic, and refuses to start if Azure rejects the key or reports a model’s deployment missing. As with the regional-endpoint check above, a transient Azure outage during that check only logs a warning and lets startup continue.
With an Azure platform key, the OpenAI models a workspace can run on that key are exactly the ones step 3 checks: the chat, rewrite, rerank, and embedding defaults. The model pickers under Settings → Providers and on an agent’s chat model override offer only those for OpenAI, and the API refuses a workspace model preference or an agent override naming any other OpenAI model, with an error that lists the models the key serves. To run another OpenAI model on the platform key, deploy it under its model name and make it one of the defaults. A workspace that brings its own OpenAI key picks from the full model list, since its calls go to OpenAI’s own hosts.
A stored OpenAI pick outside that set still answers: each chat, rewrite, or rerank call runs on the deployment’s default model for that job instead, and the backend logs a warning, Platform key cannot serve the requested LLM model; running the environment default, with the workspace, the job, the requested model, and the model that ran. The dashboard shows the stored pick as unavailable, so the operator can choose a model that runs.
A workspace’s own OpenAI key is unaffected either way: its BYOK residency choice only ever reaches OpenAI’s own hosts, never an Azure endpoint.
Anthropic and Gemini
Claude and Gemini each take either a platform-held key (ANTHROPIC_API_KEY, GEMINI_API_KEY) or a workspace’s own key. Both call their provider’s global endpoint either way; there’s no region setting for either provider.
ElevenLabs (voice channel)
A voice runtime key is a workspace’s own ElevenLabs account, set under Settings → Providers → Voice runtime keys. The region is the account’s own: an ElevenLabs account lives on exactly one region’s host — United States (the default), European Union, India, or Singapore — so when you press Connect on an agent’s Voice card, Radioso asks each host and works with the one that knows the key. The card then names it, for example recordings stay in the European Union, and calls, recordings and their deletion all go to that host. EU residency needs an Enterprise ElevenLabs account; to keep callers’ recordings in the EU, store the key of an account provisioned there and connect again.
Resend (email)
Resend sends and receives mail for the email channel. RESEND_CHANNEL_REGION picks the region of the sending domain Resend provisions for the channel — eu-west-1 for an EU deployment, us-east-1 by default. It doesn’t move Resend’s own account data, delivery metadata, logs, or received message content, which Resend keeps in the United States regardless. If your deployment needs a safeguard for that transfer — standard contractual clauses, for example — put it in place separately; this setting doesn’t provide one.
Lettermint (email)
Lettermint is the email channel’s other provider (EMAIL_CHANNEL_PROVIDER=lettermint, and MAIL_DRIVER=lettermint for transactional mail). It has no region setting because it processes and stores sent and received mail — message content, delivery metadata, and logs — in the EU regardless of which region your deployment runs in. The sub-processors that handle mail are UpCloud (Netherlands), OVH (Germany, for backups), and Postmastery (Netherlands); its US sub-processors, Stripe for payments and Slack for its own team chat, have no access to mail. Lettermint keeps message content for 28 days.
PostHog and OTEL log export
PostHog product analytics and PostHog error reporting (PRODUCT_ANALYTICS_SINKS or ERROR_SINKS including posthog, an Enterprise Edition sink) send to POSTHOG_HOST. Point it at PostHog’s EU ingestion host (https://eu.i.posthog.com) to keep that data in the EU, or its US host to keep it in the US.
OTEL log export is a separate mechanism with its own setting, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; it doesn’t read POSTHOG_HOST. As a Terraform convenience, the Terraform module defaults OTEL_EXPORTER_OTLP_LOGS_ENDPOINT to <posthog_host>/i/v1/logs when OTEL logs are enabled and no explicit endpoint is set, but a deployment that sets OTEL_EXPORTER_OTLP_LOGS_ENDPOINT directly controls its own region independently of POSTHOG_HOST.
Google Cloud Logging
On Radioso Cloud, every request and application log line goes to Google Cloud Logging in the stack’s project, and the stack decides where it’s stored. The EU stack keeps its entries in a log bucket in europe-west1 (Belgium). The US stack keeps its entries in the project’s _Default bucket, whose location is global. Both keep them for 30 days.
Error Reporting groups the errors it finds in the EU bucket under europe-west1. Two kinds of entry stay in global on every stack. Google’s admin-activity audit entries, which record which identity changed which resource, go to the project’s _Required bucket for 400 days; Google fixes that bucket’s location and retention. Entries about project-wide resources that have no region, such as a service account minting a deploy token, stay in _Default. Neither kind carries conversation content.
A deployment on the bundled Terraform stores its logs in its project’s _Default bucket until you route them. To keep an environment’s entries in its own region, set regional_log_bucket_enabled = true in its foundation root (infra/terraform/foundation/environments/<env>/main.tf). That creates a radioso-<env>-logs bucket in the environment’s region with 30-day retention, a sink that routes every entry about a resource in that region and about the environment’s Cloud SQL instance to it, and an exclusion that keeps those entries out of _Default. The filter takes every entry in the region, so the environment has to be the only one in its region within the project.
Apply it in two steps, because Google takes a few minutes to apply a sink or exclusion change, and an exclusion that lands first drops those minutes of entries:
cd infra/terraform/foundation/environments/<env>
export GOOGLE_OAUTH_ACCESS_TOKEN="$(gcloud auth print-access-token)"
terraform init -backend-config=backend.hcl
terraform apply -target=module.foundation.google_logging_project_sink.regional
gcloud logging read 'resource.type="cloud_run_revision"' --project=<project> \
--bucket=radioso-<env>-logs --location=<region> --view=_AllLogs --limit=3 --freshness=10m
terraform applyRun the last apply once the read returns entries. Entries written before the apply stay in _Default until its retention removes them.
Read the regional bucket by naming its view. That needs the logging.views.access permission, which the Private Logs Viewer and Logs View Accessor roles carry and Logs Viewer doesn’t:
gcloud logging read 'resource.type="cloud_run_revision" AND severity>=ERROR' \
--project=<project> --bucket=radioso-<env>-logs --location=<region> --view=_AllLogs \
--limit=5 --freshness=1hSlack and Stripe
The Slack and Stripe integrations each reach their provider’s global, US-based API. Neither has a region setting.
Read next
- Deployment — the production contract and required configuration categories these settings sit inside.
- Email channel — connect a mailbox and verify sending.