How to safely add paid AI features to a desktop app
By Flavio Copes
Protect your AI API keys and margins with server-side authentication, per-user budgets, rate limits, and a global spending cap.
You sell a desktop application for $49.
The customer pays once, downloads it, and starts using it. But every time they click Generate, your application calls an AI model.
You get the bill for that call.
This changes the economics of desktop software. Selling the application does not end your financial obligation. A customer can keep generating tokens for months or years.
We need an architecture that puts a maximum price on that risk.
Why a lifetime license can become a liability
Traditional desktop software has a nice property. Once the customer downloads it, most of the work happens on their computer.
An AI feature changes this. Each request consumes a paid resource.
Suppose one generation costs you $0.04. A customer runs 10 generations across 5 projects every week:
10 generations × 5 projects = 50 generations/week
50 × 4.33 = 217 generations/month
217 × $0.04 = $8.68/month
At that rate, a $49 lifetime license pays for less than six months of inference. We have not included payment fees, hosting, support, taxes, or profit.
The useful equation is:
purchase price - lifetime AI cost = gross profit
The problem is the word lifetime. If AI usage has no limit, its lifetime cost has no limit either.
Never put your model API key in the application
The most dangerous architecture is also the easiest one to build:
Desktop app → AI provider
The application contains your Anthropic, OpenAI, or Google API key. It sends requests directly to the provider.
This creates two problems.
First, you cannot enforce a trustworthy usage limit. The customer controls the computer running your application. They can modify the application, replay requests, or bypass your interface.
Second, they can extract the API key. Obfuscating it makes extraction harder, but does not make the secret safe.
If you distribute a secret to customers, treat it as public.
Once someone has the key, they do not need your application anymore. They can call the provider directly while you pay the bill.
Put your server in the middle
The model provider should only accept requests from infrastructure you control.
The architecture becomes:
Desktop app
↓
Your backend
↓
AI Gateway
↓
AI provider
The desktop application sends a license or session token to your backend. Your backend validates it and finds the customer account.
Only then does it send the AI request.
Your backend now decides:
- whether the license is valid
- which customer is making the request
- which model they can use
- how quickly they can send requests
- how much they can spend
- whether the request should continue
The model API key stays in a server-side secret. It never reaches the desktop application.
If you want to build this layer on Cloudflare, a Worker is a good fit. My free Cloudflare course also covers Workers, AI Gateway, limits, and production operations.
I also have a complete Cloudflare AI Gateway guide covering setup, authentication, logging, caching, and model calls.
Do not trust a user ID from the client
The desktop application might send this:
{
"userId": "customer_123",
"prompt": "Summarize this project"
}
Do not use that userId for billing.
A customer can change it to customer_456. Your system would then assign their usage to someone else.
Instead, authenticate the request first:
Desktop app sends license token
↓
Backend validates the token
↓
Backend finds customer_123
↓
Backend attaches trusted customer metadata
↓
AI Gateway receives the request
The customer can send any userId they want. Your backend ignores it and derives the identity from the token.
This is the boundary that makes per-customer limits meaningful.
Add the trusted customer ID to each model request
Once your backend knows the customer, it can attach that identity to the AI Gateway request.
Cloudflare AI Gateway accepts custom metadata through the cf-aig-metadata header. Here is the important part of a Worker request:
const customer = await authenticateLicense(request, env)
const response = await fetch(env.AI_GATEWAY_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${env.AI_GATEWAY_TOKEN}`,
'Content-Type': 'application/json',
'cf-aig-collect-log-payload': 'false',
'cf-aig-metadata': JSON.stringify({
user_id: customer.id,
product: 'project-writer',
}),
},
body: JSON.stringify({
model: 'openai/gpt-5.5',
messages: [
{
role: 'user',
content: 'Summarize the current project',
},
],
}),
})
authenticateLicense() represents your own authentication or licensing system. It must reject invalid, expired, and revoked tokens.
AI_GATEWAY_URL contains your server-side gateway endpoint. The desktop application never receives that endpoint’s credentials.
The important detail is where customer.id comes from. The backend creates it after authentication. The desktop application does not choose it.
Do not include email addresses, prompts, license keys, or other secrets in metadata. Metadata appears in gateway logs.
Cloudflare Access provides another option. When a request reaches an Access-protected AI Gateway custom domain with a valid user identity, Cloudflare adds a trusted cf.user_id value. This can remove the need to pass your own user identifier.
For a consumer desktop application, I would usually keep my product authentication in the backend. It gives me direct control over licenses, plans, refunds, and credit purchases.
Use three separate limits
One limit cannot protect you from every failure.
I would add three layers.
Limit how quickly customers can send requests
A rate limit controls speed.
For example:
20 requests per customer per hour
This catches retry loops, automation, and obvious abuse. It also protects the rest of your backend.
Apply this limit using the authenticated customer ID, not only the IP address. Many customers can share an IP. One customer can also change networks.
An IP limit still helps as a second abuse signal. It should not be your customer identity.
Limit how much each customer can spend
A spending limit controls money.
For example:
$5 per customer per month
Cloudflare AI Gateway spend limits can split a rule by custom metadata such as user_id. Each customer then receives an independent budget.
When a customer reaches the limit, the gateway returns a 429 response until the budget window resets. Your application should translate that into a useful message:
You used this month's included AI allowance.
It resets on October 1, or you can add more credits now.
Do not show a generic network error. Reaching the allowance is a product state, not a broken connection.
Limit the entire product
Finally, add a global spending limit.
For example:
$500 across the entire application per month
This is the circuit breaker.
Imagine that a bug skips authentication. Or your customer metadata is missing. Or a new release enters a request loop.
The global limit prevents a product bug from becoming an unlimited bill.
Rate limits and spending limits solve different problems
A rate limit answers:
How quickly can this customer use the feature?
A spending limit answers:
What is the maximum this customer can cost me?
You need both.
Ten small requests and ten large requests can have very different prices. A customer can stay under a request limit while sending huge prompts and asking for long responses.
Tokens are closer to cost than request counts. Dollars are closer still.
I would also restrict input size and output tokens at the application layer. If the feature only needs a 500-word summary, do not accept a 2-million-token project and allow a 30,000-token response.
Know what a hard limit cannot guarantee
Cloudflare AI Gateway spend limits block requests when the tracked budget is exceeded. But cost tracking is an estimate based on token counts and known model prices.
The limits are also eventually consistent. The current request’s cost is recorded after it completes. Several concurrent requests can briefly take an account over its budget.
This means $5 per month should be treated as a strong control, not a mathematically exact ceiling.
Leave margin in your unit economics. If spending $5.20 instead of $5 would break the product, the product does not have enough margin.
Use the model provider’s billing controls as another boundary when it offers hard enforcement. Check the wording carefully. Some dashboards call a notification threshold a “budget,” but only send an alert after you cross it.
An alert is not a circuit breaker.
Decide what happens after the allowance is used
You have four useful pricing models.
Include a fixed number of generations
You can sell the application with a clear allowance:
$49 includes 500 AI generations
This is easy to explain, but generations do not all cost the same. You still need token and spending controls behind the scenes.
Include a dollar-based allowance
You can include a fixed inference budget:
$49 includes $5 of managed AI usage
This maps directly to your cost. Customers may find credits or generations easier to understand, so you can show friendly credits in the interface while tracking dollars internally.
Charge a subscription
A subscription aligns recurring revenue with recurring AI costs:
$19/month including up to $5 of AI usage
Your maximum inference cost resets with the customer’s payment. This makes the margin easier to model.
Let customers bring their own key
With bring your own key, or BYOK, the customer enters their provider key:
Customer → their AI account → their bill
Your application sells the workflow. The customer pays for inference.
BYOK works well for power users. It adds setup friction for everyone else and gives you less control over model availability.
I like the hybrid model:
- regular customers receive a small managed allowance
- power users can add their own provider key
This gives new customers a feature that works immediately. Heavy users do not create an unlimited cost.
Calculate the allowance before setting the price
Start with one complete operation.
A generation often includes more than one model call. You might classify input, retrieve context, generate a result, and validate it.
Measure the cost of the complete workflow:
input tokens
+ cached input tokens
+ output tokens
+ tool calls
+ retries
= cost per operation
Then estimate normal monthly usage:
cost per operation × operations per month
= expected AI cost per customer
Run the same calculation for a heavy customer. Averages do not protect you from the expensive end of the distribution.
If you want to compare models and workload assumptions, use my AI inference cost calculator. Put in your real input size, output size, calls per user, and active users.
Your allowance should come from this calculation. Do not choose $5 because it looks reasonable.
The architecture I would use
For a paid desktop application, I would start here:
Desktop application
↓
Product authentication and license validation
↓
Server-side API
↓
Per-customer rate limit
↓
AI Gateway with per-customer spend limit
↓
AI provider
I would also add:
- a global gateway spending limit
- strict input and output token limits
- separate development and production gateways
- logs without stored prompt and response bodies when content is sensitive
- a clear allowance screen inside the application
- a BYOK option for heavy users
The desktop application would contain no unrestricted model credentials.
I would not build all of this for a local feature using a model that runs entirely on the customer’s computer. There is no paid inference account to protect in that case.
I would also avoid lifetime managed AI for an unpredictable workload. A subscription, prepaid credits, or BYOK gives the business a much safer shape.
Put a number on your worst case
AI usage is another cloud resource.
You would not give every customer unlimited access to your AWS account. Do not give them unlimited access to your AI budget.
Before shipping, you should be able to answer three questions:
What can one customer cost me this month?
What can all customers cost me this month?
What happens when either limit is reached?
If any answer is “unlimited,” the feature is not ready.
Want me to talk about your product? You can sponsor this site.
Related posts about ai: