Example finding How it works Coverage PENTEST METHODOLOGY DOCS PRICING FAQ MCP CONTACT LOG IN SIGN UP →

People use my AI app as a free ChatGPT: how to stop it

FIX / AI FREE RIDERS / 9 MIN READ

People use your AI app as a free ChatGPT because your server route forwards any message to the model, so the fix is to make the server decide the task, the model, the length, and the quota. The browser must only send the user text. The server must add the instructions, set the limits, and count the use of each user.

This guide shows how to confirm the problem, how to stop it in order of urgency, and how to check what it cost. It also shows how to prevent the problem in the next release.

What does “people use my AI app as a free ChatGPT” mean?

It means that users, or scripts, send requests to your AI route for tasks that your product does not offer. You pay for each request. The users pay nothing.

Your AI feature has a narrow purpose. For example, it writes product descriptions or answers questions about your documents. The model behind it can do much more. If your route does not restrict the model, anyone can ask it to write code, translate text, or do homework.

There are three common causes:

  1. No login on the route. Anyone on the internet can call it, with no account.
  2. No fixed instructions on the server. The browser sends the system prompt, or the full request body, so the caller controls the task.
  3. No quota. One user can send as many requests as they want.

Vibe coding tools often create the first two causes. You ask the tool for a chat feature, and it creates a route that forwards the request body to the provider. The feature works in a demo. In production, the route is an open proxy.

An open proxy is also a security problem and not only a cost problem. A caller who controls the whole request can select an expensive model, request very long output, and use tools that you did not plan to expose. If the key is in the browser, read the guide on an exposed OpenAI key first. This guide assumes that the key is already on the server.

How do I confirm that people abuse my route?

Run three checks. Each check takes a few minutes.

Check 1: Call the route without the UI

  1. Open your live site in a browser and open DevTools.
  2. Select the Network tab and use the AI feature once.
  3. Find the request to your AI route. Right-click it and select Copy as cURL.
  4. Paste the command into a terminal. Remove the cookie and the Authorization header.
  5. Run the command.

If the route returns an answer, anyone can call it without a login. Change the message in the body to Write a Python function that sorts a list. Run the command again. If the route returns code, the route does not restrict the topic.

Warning: Run this test only against your own app. Do not paste the copied command into a public chat, because it can contain a session token.

Check 2: Look at the request body

Read the request body in the Network tab. Look for these fields:

Field in the body What it means
model The caller selects the model.
system or a message with the role system The caller writes the instructions.
max_tokens or max_output_tokens The caller selects the output length.
tools The caller selects the tools.

If any field is present, the server accepts it from the browser. An attacker can change it.

Check 3: Look at your usage data

Open the usage page in the dashboard of your AI provider. The OpenAI spend limits guide explains the project limits. Compare the number of requests with the number of real users. A few accounts with thousands of requests each is a sign of abuse. A flat, constant volume at all hours is a sign of a script.

How do I stop it now? Fix it in order of urgency

Do the steps in this order. Steps 1 and 2 limit the cost today. The other steps remove the cause.

  1. Set a hard spend limit on the project. In the OpenAI dashboard, open the project settings and select Limits. Turn on the hard limit. After the project reaches the limit, requests fail with a 429 error. A limit stops the cost, but it also stops your real users, so use it only as the last control.
  2. Require a login on the route. Return a 401 error if the request has no valid session. This stops anonymous scripts at once. If your app has no login yet, add one before you add a free tier.
  3. Remove the fields that the caller controls. Accept only the user text from the browser. Set the model, the instructions, the output length, and the tools on the server.
  4. Fix the instructions on the server. Write a short system prompt that names the one task. Add it on every request.
  5. Cap the input and the output. Reject input above a fixed number of characters. Set max_output_tokens in the request.
  6. Add a daily quota for each user. Count the requests of each user in your database. Return a 429 error after the limit.
  7. Send a user identifier to the provider. This helps the provider find abuse by one user. See the section below.
  8. Deploy and test again. Repeat Check 1 on the new deployment.

Caution: Step 3 can break your frontend. Change the frontend and the route in the same release.

The route after the fix

This example uses a Next.js route handler. The functions getUserFromSession, countToday, and recordCall are placeholders. Use the session check of your auth library and a table in your database.

import { NextResponse } from "next/server";

const SYSTEM_PROMPT =
  "You write short product descriptions for an online shop. " +
  "If the user asks for anything else, answer: 'I can only help with product descriptions.'";

const DAILY_LIMIT = 30;

export async function POST(req: Request) {
  const user = await getUserFromSession(req);
  if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  if ((await countToday(user.id)) >= DAILY_LIMIT) {
    return NextResponse.json({ error: "Daily limit reached" }, { status: 429 });
  }

  const { text } = await req.json();
  if (typeof text !== "string" || text.length === 0 || text.length > 1500) {
    return NextResponse.json({ error: "Bad request" }, { status: 400 });
  }

  await recordCall(user.id);

  const upstream = await fetch("https://api.openai.com/v1/responses", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
    },
    body: JSON.stringify({
      model: "gpt-4o-mini",
      instructions: SYSTEM_PROMPT,
      input: text,
      max_output_tokens: 400,
      safety_identifier: user.hashedId,
    }),
  });

  return new NextResponse(upstream.body, {
    status: upstream.status,
    headers: { "Content-Type": "application/json" },
  });
}

The route ignores every field of the body except text. The Responses API reference lists instructions, max_output_tokens, and safety_identifier as optional parameters.

The route records the call before it calls the provider. If you record after, a user can send many parallel requests and pass the limit. For a strict limit, use an atomic counter in your database or a rate-limit service.

Does a system prompt stop people from using the app as a free ChatGPT?

No. A system prompt reduces off-topic answers, but it does not block them. A user can write a message that tells the model to ignore the instructions. Models follow instructions with high probability, not with certainty.

The OpenAI safety best practices agree with this approach. They state that prompt engineering “can help constrain the topic and tone of output text”. They also state that limiting the amount of text a user can enter helps to avoid prompt injection, and that limiting the number of output tokens helps to reduce the chance of misuse.

Use the system prompt as the first control and not as the only control. These controls work even when the prompt fails:

Control What it limits
Login Anonymous scripts
Daily quota per user The cost of one account
Input size cap The size of a request
Output token cap The cost of one answer
Fixed model The price per token
Global daily cap The total cost of all free accounts

With these controls, a user who gets one off-topic answer costs you a few cents. A user who tries to get thousands of answers reaches the quota.

Add a topic check for strict products

Some products must refuse all off-topic requests. For these products, add a check before the main request. You have two options:

  1. Use a cheap classifier call. Send the user text to a small model with the instruction Answer yes or no. Is this text about product descriptions? If the answer is not yes, return a refusal and skip the main call.
  2. Use the moderation endpoint for content safety. According to the OpenAI moderation guide, the moderation endpoint is free to use. It classifies harmful content. It does not check the topic, so it does not replace the classifier.

A classifier adds cost and delay to each request. Use it only if off-topic use is a real cost for you. For most products, the quota is enough.

How do I send a user identifier to OpenAI?

Send a stable, hashed identifier in the safety_identifier field. The OpenAI safety best practices state that a safety identifier “should be a string that uniquely identifies each user”. They also say to hash the username or email address, so that you do not send identifying information.

Use a one-way hash with a secret value on the server. Do not send the raw email address. If you offer a preview to users who are not logged in, the same page says that you can send a session ID instead.

The identifier does not stop abuse by itself. It lets the provider connect abusive requests to one user, and not to your whole organization. This protects your other users if one user sends harmful requests.

How do I check what the abuse cost?

Read three places. Do this after you deploy the fix, so that the data stops growing while you read it.

  1. The provider usage page. Open the usage data for the project. Look at the daily cost and the number of requests. Find the first day on which the numbers are much higher than your user count can explain.
  2. The model list. Look for models that your app does not call. If you see one, the caller controlled the model, and the old route accepted the field.
  3. Your own request log. If your route logs the user ID, group the calls by user. Use a query like this one:
select user_id, count(*) as calls, min(created_at) as first_call
from public.ai_calls
where created_at > now() - interval '30 days'
group by user_id
order by calls desc
limit 20;

Compare the top users with normal users. A normal user sends a few requests each day. An abusive user sends hundreds.

If the route had no login, you have no user IDs. Read the logs of your host instead. Group the requests to the AI route by IP address and by user agent. Many requests from one address with the same user agent are a script.

Also check whether the abuse reached your own data. If the route passes documents or database rows to the model, a caller could ask the model to repeat them. Read the pattern on indirect prompt injection to see how this works.

Note: The usage page shows cost and volume. It does not show the content of the prompts. You cannot tell from the page what the users asked.

How do I stop it from coming back?

Use these controls in every AI feature you ship.

  1. Treat every AI route as a paid route. A route that spends money needs the same controls as a payment route: login, limits, and logs.
  2. Keep all model parameters on the server. The browser sends the user text. It does not send the model name, the system prompt, the tools, or the token limits.
  3. Set a global daily cap. Count all calls for all users. Return a 503 error when the count reaches your daily budget. This limits the loss when many accounts attack together.
  4. Add friction to account creation. Require email confirmation before the first AI call. Add a CAPTCHA if you see many new accounts from one source.
  5. Match the quota to the price. If a plan costs 10 dollars each month, set the quota so that a user who reaches it costs you less than that.
  6. Set spend alerts below the hard limit. An alert tells you about a spike before the limit stops the feature.
  7. Review AI-generated code for open routes. Search each pull request for routes that call a model provider. Check that each one has a login check and a limit.
  8. Test the live route after each release. Run Check 1 again. A change by a coding tool can remove a control without a warning.

For more patterns on bot protection and token budgets, read the API abuse protection guide.

When should I get help?

Get help in these cases:

  • The usage page shows large charges and you cannot find the cause. Contact the support of your AI provider. Give the project ID and the dates.
  • Your route passes private data to the model, such as documents or customer records. Ask a security engineer to review the route.
  • The traffic comes from many IP addresses and many new accounts. A simple quota does not stop this. Ask your host about bot protection.
  • You cannot tell which user sent which request. Add logging first. Then repeat the checks.

After the fix, run Check 1 one more time. Remove the login and the headers, send the off-topic message, and confirm that the route returns a 401 error.

Common questions

Can I stop off-topic use with the system prompt alone?
No. A system prompt reduces off-topic answers, but it does not stop them. Users can write messages that override it. Use the prompt together with a quota and token caps.
Is it a problem if real users ask general questions?
It is a cost problem, not a security problem. Decide how much general use you accept. Then set a quota that matches your price per user.
Do I need to log the prompts of my users?
You need some record to find abuse. Store the user ID, the time, and the token count. Store the prompt text only if your privacy policy allows it.
Does a free tier make this worse?
Yes. A free tier with no card gives an attacker unlimited new accounts. Add email confirmation and a global daily cap for all free users.
Should I block users who abuse the route?
Yes. Add a flag on the user row and check it in the route. Block the account, not only the IP address.

Test your ai route

A VibeEval scan sends requests to the AI routes of your deployed app and reports routes that work without a login or accept parameters from the caller.

14-day free trial · No credit card · Cancel anytime

SCAN YOUR APP →