{"id":17726,"date":"2026-04-29T15:57:40","date_gmt":"2026-04-29T07:57:40","guid":{"rendered":"https:\/\/five.co\/?p=17726"},"modified":"2026-05-12T10:43:35","modified_gmt":"2026-05-12T02:43:35","slug":"multi-tenant-setups-in-vapi","status":"publish","type":"post","link":"https:\/\/preview-php85.dzine.org\/five\/blog\/multi-tenant-setups-in-vapi\/","title":{"rendered":"Multi-Tenant Setups in Vapi: Key Security and Cost Tracking"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Vapi API Key Security: A Guide to Multi\u2011Tenant Setups and Cost Tracking<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You\u2019ve built a brilliant voice agent with Vapi. The prompt is polished, response times are human-like, and the AI handles edge cases like a pro. Your client wants it embedded on their website. You drop in the Vapi Web SDK snippet, replace <code>YOUR_PUBLIC_API_KEY<\/code>, and push it live. The widget works perfectly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then you open Chrome DevTools, glance at the Network tab, and your stomach drops. There\u2019s your public API key &#8211; sitting in plain sight, readable by anyone who visits the page! Could someone copy that key and rack up thousands of dollars in calls from a rogue domain? How do I bill this client for <em>their<\/em> usage without also paying for everyone else\u2019s? And when I onboard my next client, how do I keep their data and costs completely separate?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is the API key security and multi\u2011tenancy gap that hits every voice AI agency. Vapi gives you the keys (literally) to build an agent, but <strong>securing those keys for client delivery, and tracking costs per client, requires architecture that Vapi doesn\u2019t spell out in the QuickStart guide<\/strong>. In this post, I\u2019ll walk you through that architecture: from key types and origin restrictions to per\u2011organization billing and the proxy pattern that locks everything down.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">By the end, you\u2019ll have a production\u2011ready model for delivering Vapi voice agents to multiple clients securely, with accurate per\u2011client cost tracking &#8211; and I\u2019ll show you how Five automates the whole thing.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Two\u2011Key System: Public vs. Private API Keys<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Vapi provides two types of API keys, and understanding the difference is the foundation of a secure multi\u2011tenant setup:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Public key<\/strong> \u2014 Designed to be used in client\u2011side code. It can create calls and websocket connections, but it <strong>cannot<\/strong> read assistant configurations, modify settings, or access billing. It\u2019s meant to be exposed, but that doesn\u2019t mean it should be unrestricted.<\/li>\n\n\n\n<li><strong>Private key<\/strong> \u2014 Used for server\u2011side operations: creating and updating assistants, pulling analytics, managing phone numbers, and retrieving cost data. This key must never touch a browser.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The naive approach, i.e. using a single public key for all clients, is the security equivalent of leaving your front door unlocked. Yes, the public key is limited, but without additional restrictions, anyone who grabs it from your client\u2019s website can spin up calls from any domain, exhausting your account balance. Worse, because all usage flows through one key, you have no way to know which client generated which costs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The solution: <strong>per\u2011client public keys, scoped to specific domains, associated with unique organizations<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Multi\u2011Tenancy with Organizations: Isolating Clients<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/vapi.ai\/blog\/organizations\" rel=\"nofollow noopener\" target=\"_blank\">Vapi\u2019s Organization (Org) model<\/a> is the foundation for multi\u2011tenancy. Each organization is a separate workspace with its own assistants, phone numbers, knowledge bases, and \u2014 critically \u2014 its own API keys and usage logs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For an agency, the pattern is:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Your master Org<\/strong> \u2014 Where you build and test agents. Private key only, never shared.<\/li>\n\n\n\n<li><strong>Client\u2011specific Orgs<\/strong> \u2014 Created for each client you serve. Each Org gets its own public (and optionally private) keys, plus its own set of assistants.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">When you move to production, you generate a unique public API key <strong>inside the client\u2019s Org<\/strong>. That key is then embedded in their widget. The result: Client A\u2019s calls are completely isolated from Client B\u2019s. Client A\u2019s key has no access to Client B\u2019s assistants, call logs, or billing data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is the \u201ccontainer\u201d model that agencies ask about constantly. It takes a few steps to implement manually, but it\u2019s straightforward once you understand Vapi\u2019s hierarchy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Master Account\n \u2514\u2500 Org: Your Agency (private key only)\n \u2514\u2500 Org: Client A\n \u2502   \u2514\u2500 Public Key: \"pk_client_a_...\"\n \u2502   \u2514\u2500 Assistant: \"Receptionist - Client A\"\n \u2502   \u2514\u2500 Phone Number: +1 (555) 100-1001\n \u2514\u2500 Org: Client B\n     \u2514\u2500 Public Key: \"pk_client_b_...\"\n     \u2514\u2500 Assistant: \"Booking Bot - Client B\"\n     \u2514\u2500 Phone Number: +1 (555) 200-2002<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Locking Down Public Keys with Allowed Origins<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A per\u2011client public key is a huge step forward, but without origin restrictions it\u2019s still vulnerable. Someone could copy Client A\u2019s public key and embed it on their own site, using your client\u2019s account to handle calls for free.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Vapi lets you bind a public API key to a list of <strong><a href=\"https:\/\/docs.vapi.ai\/customization\/jwt-authentication\" rel=\"nofollow noopener\" target=\"_blank\">Allowed Origins<\/a><\/strong>. These are the domains (and ports) from which the key can be used. If a call\u2011creation request arrives from a domain not on the list, Vapi rejects it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To set this up when generating a public key:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Navigate to the client\u2019s Organization in Vapi.<\/li>\n\n\n\n<li>Create a new Public API Key.<\/li>\n\n\n\n<li>Under <strong>Allowed Origins<\/strong>, add the exact domains that will host the widget \u2014 e.g., <code>https:\/\/www.client-a-website.com<\/code>, <code>https:\/\/app.client-a-website.com<\/code>.<\/li>\n\n\n\n<li>Save the key and embed it in the client\u2019s widget code.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Now, even if the key is leaked, it\u2019s useless on any other domain. This is the standard technique recommended by Vapi and by experienced agency developers exploring \u201ccustomer\u2011specific public API keys that can be tied to allowed origins.\u201d<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For developers who need to test on localhost, you can temporarily add <code>http:\/\/localhost:3000<\/code> to the allowed origins during development and remove it for production.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Per\u2011Client Cost Tracking: Using OrgIDs to Calculate Charges<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The security piece solved, the next question is always billing: <em>\u201cHow do I calculate the charge for that specific API key?\u201d<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The answer lies in the Organization ID (OrgID) associated with each key. Vapi tracks usage per Organization, and you can pull that data through the dashboard or the API to build per\u2011client invoices.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Retrieve Usage Per Org<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Using Vapi\u2019s Analytics API, you can fetch call logs filtered by OrgID. A call to the reporting endpoint returns data that includes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Total calls<\/strong> in the date range<\/li>\n\n\n\n<li><strong>Total minutes<\/strong> (broken down by provider)<\/li>\n\n\n\n<li><strong>Costs<\/strong> for orchestration (Vapi\u2019s per\u2011minute fee), STT, TTS, LLM, and telephony<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If you\u2019re using the Vapi dashboard, navigate to the client\u2019s Org, go to <strong>Analytics<\/strong>, and you\u2019ll see a cost breakdown for that Org alone.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Calculate Client\u2011Facing Charges<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You\u2019re not just passing through Vapi\u2019s cost \u2014 you\u2019re running a business. You\u2019ll add your markup to the underlying costs. The real\u2011world all\u2011in cost for a Vapi call lands between $0.23 and $0.33 per minute when you sum orchestration, Deepgram\/Whisper transcription, OpenAI\/Anthropic LLM, ElevenLabs\/Play\/TTS, and Twilio\/Vonage telephony.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A common agency pricing model:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Pass\u2011through plus margin<\/strong>: Multiply total Vapi cost by 1.3\u20111.5x<\/li>\n\n\n\n<li><strong>Flat per\u2011minute rate<\/strong>: Charge $0.40\/minute regardless of underlying cost<\/li>\n\n\n\n<li><strong>Tiered subscription<\/strong>: $299\/month for up to 1,000 minutes, then $0.35\/minute overage<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">With per\u2011Org usage data, you can run the numbers at the end of each billing cycle and generate a Stripe invoice. No more manual spreadsheet detective work.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: Automate, Don\u2019t Hunt for Data Monthly<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Doing this manually across 10+ clients is soul\u2011crushing. Every month you\u2019re logging into Vapi, switching Orgs, exporting CSVs, calculating totals, and manually creating Stripe invoices. It\u2019s the kind of repetitive work that prevents agencies from scaling.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The long\u2011term solution is a system that <strong>pulls per\u2011Org analytics automatically, applies your pricing model, and generates invoices in Stripe<\/strong>. That\u2019s exactly what the <a href=\"https:\/\/preview-php85.dzine.org\/five\/blog\/how-to-white-label-vapi-voice-ai-agents-the-3-best-options\/\">Five white\u2011label Vapi portal does<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Advanced Security: The Proxy Server Pattern<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For agencies with the highest security requirements, there\u2019s one more pattern worth knowing: <strong>proxy all client calls through your own server<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of embedding a Vapi public key in the widget at all, you:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Build a lightweight backend endpoint on your own server.<\/li>\n\n\n\n<li>Store the Vapi public key <strong>only on the server<\/strong>, never in client\u2011side code.<\/li>\n\n\n\n<li>The client\u2019s website sends call\u2011creation requests to your endpoint.<\/li>\n\n\n\n<li>Your server validates the request, authenticates the client session, and forwards the call creation to Vapi using the appropriate per\u2011Org key.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The result: the end\u2011user\u2019s browser never sees a Vapi API key. It\u2019s the most secure approach, and it also opens the door to additional value \u2014 like logging, custom analytics, and per\u2011call business logic \u2014 before the call even starts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The tradeoff is latency and complexity. You\u2019re introducing a hop between the user and Vapi\u2019s infrastructure. For most agencies, properly restricted per\u2011Org public keys with allowed origins provide sufficient security without the overhead.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Build Secure Multi\u2011Tenant Voice Apps Without the API Key Headache<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You\u2019ve seen the architecture. You know the patterns. Now let\u2019s get you running with it \u2014 without the months of build time. Check out our white-label Vapi portal to secure your Vapi agents properly.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong><a href=\"https:\/\/preview-php85.dzine.org\/five\/contact-us\/\">Request a Trial<\/a><\/strong> \u2014 We\u2019ll spin up a dedicated Five instance.<\/li>\n\n\n\n<li><strong>Connect Your Vapi Account<\/strong> \u2014 Your assistants and phone numbers sync instantly.<\/li>\n\n\n\n<li><strong>Onboard a Client<\/strong> \u2014 Five provisions the user credentials automatically.<\/li>\n\n\n\n<li><strong>Go Live<\/strong> \u2014 Your client gets a branded portal, accessible only from their domain, with zero Vapi\u2011branded surfaces.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Stop guarding API keys and start scaling your voice AI business.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Vapi API Key Security: A Guide to Multi\u2011Tenant Setups and Cost Tracking You\u2019ve built a brilliant voice agent with Vapi. The prompt is polished, response times are human-like, and the AI handles edge cases like a pro. Your client wants it embedded on their website. You drop in the Vapi Web SDK snippet, replace YOUR_PUBLIC_API_KEY, [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":17736,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_eb_attr":"","_uag_custom_page_level_css":"","footnotes":""},"categories":[21],"tags":[],"class_list":["post-17726","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog"],"acf":[],"uagb_featured_image_src":{"full":["https:\/\/preview-php85.dzine.org\/five\/wp-content\/uploads\/2026\/04\/Five.Co-Multi-Tenant-Setups-in-Vapi-Key-Security-and-Cost-Tracking.jpg",1280,720,false],"thumbnail":["https:\/\/preview-php85.dzine.org\/five\/wp-content\/uploads\/2026\/04\/Five.Co-Multi-Tenant-Setups-in-Vapi-Key-Security-and-Cost-Tracking-150x150.jpg",150,150,true],"medium":["https:\/\/preview-php85.dzine.org\/five\/wp-content\/uploads\/2026\/04\/Five.Co-Multi-Tenant-Setups-in-Vapi-Key-Security-and-Cost-Tracking-300x169.jpg",300,169,true],"medium_large":["https:\/\/preview-php85.dzine.org\/five\/wp-content\/uploads\/2026\/04\/Five.Co-Multi-Tenant-Setups-in-Vapi-Key-Security-and-Cost-Tracking-768x432.jpg",768,432,true],"large":["https:\/\/preview-php85.dzine.org\/five\/wp-content\/uploads\/2026\/04\/Five.Co-Multi-Tenant-Setups-in-Vapi-Key-Security-and-Cost-Tracking-1024x576.jpg",1024,576,true],"1536x1536":["https:\/\/preview-php85.dzine.org\/five\/wp-content\/uploads\/2026\/04\/Five.Co-Multi-Tenant-Setups-in-Vapi-Key-Security-and-Cost-Tracking.jpg",1280,720,false],"2048x2048":["https:\/\/preview-php85.dzine.org\/five\/wp-content\/uploads\/2026\/04\/Five.Co-Multi-Tenant-Setups-in-Vapi-Key-Security-and-Cost-Tracking.jpg",1280,720,false]},"uagb_author_info":{"display_name":"Dominik Keller","author_link":"https:\/\/preview-php85.dzine.org\/five\/author\/f1v5_ed3_my\/"},"uagb_comment_info":0,"uagb_excerpt":"Vapi API Key Security: A Guide to Multi\u2011Tenant Setups and Cost Tracking You\u2019ve built a brilliant voice agent with Vapi. The prompt is polished, response times are human-like, and the AI handles edge cases like a pro. Your client wants it embedded on their website. You drop in the Vapi Web SDK snippet, replace YOUR_PUBLIC_API_KEY,&hellip;","_links":{"self":[{"href":"https:\/\/preview-php85.dzine.org\/five\/wp-json\/wp\/v2\/posts\/17726","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/preview-php85.dzine.org\/five\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/preview-php85.dzine.org\/five\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/preview-php85.dzine.org\/five\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/preview-php85.dzine.org\/five\/wp-json\/wp\/v2\/comments?post=17726"}],"version-history":[{"count":3,"href":"https:\/\/preview-php85.dzine.org\/five\/wp-json\/wp\/v2\/posts\/17726\/revisions"}],"predecessor-version":[{"id":17886,"href":"https:\/\/preview-php85.dzine.org\/five\/wp-json\/wp\/v2\/posts\/17726\/revisions\/17886"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/preview-php85.dzine.org\/five\/wp-json\/wp\/v2\/media\/17736"}],"wp:attachment":[{"href":"https:\/\/preview-php85.dzine.org\/five\/wp-json\/wp\/v2\/media?parent=17726"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/preview-php85.dzine.org\/five\/wp-json\/wp\/v2\/categories?post=17726"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/preview-php85.dzine.org\/five\/wp-json\/wp\/v2\/tags?post=17726"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}