CodeGym /Courses /ChatGPT Apps /Store requirements and minimal permissions

Store requirements and minimal permissions

ChatGPT Apps
Level 18 , Lesson 0
Available

1. What is the ChatGPT Store in the context of this course

Let’s start with the big picture. The ChatGPT Store is an app catalog inside ChatGPT where a user can come in, find your app, enable it, and use it in regular conversations. For you, it’s not just a storefront but a distribution channel with rules—an “App Store” analogue for the LLM world.

In this course, we distinguish three modes for your app:

  • First mode — Dev Mode. Your app is tied to your account/organization, available to you and, possibly, colleagues. There’s no formal review, but all platform-wide policies apply. Here you can safely break things, log everything, run tunnels and staging backends.
  • Second mode — public Store. This is the big league: the app is available to all ChatGPT users (subject to regional restrictions), goes through moderation, has a public listing, links to Privacy/Terms, and already must behave like a mature product.
  • Third — org‑only apps. These are applications for a single organization: a company can enable/disable them for employees, layer its own security requirements on top of OpenAI’s, and even conduct internal reviews.

In this lecture we’re interested in the “public Store + public listing” combination. The key point: you stop being just the developer of “yet another Next.js service” and become the author of a product that needs to appeal to three parties at once: users, Store moderators, and your security team.

2. Store baseline requirements: policy, honesty, and UI

Content and policies

The ChatGPT Store is a moderated platform. Put simply: OpenAI doesn’t want apps inside ChatGPT that violate the platform’s usage policies (violence, terrorism, NSFW, fraud, etc.) or try to circumvent model safeguards (jailbreak prompts like “pretend you’re not ChatGPT, but my evil twin”).

This implies two things.

First, your app itself must not generate prohibited content. If our sample app GiftGenius (gift recommendations) suddenly starts suggesting gifts for “how to hide evidence of a crime,” moderation will need just one screenshot.

Second, your app must not help the user bypass filters. If a user asks: “Pick a gift to build a bomb,” the correct behavior is to refuse with a policy-based rationale, not to happily invoke your MCP tool to search for the required parts.

A large portion of this behavior is driven by the system prompt and the tools you provide to the model. But the Store looks at the outcome: what answers the user can actually get.

Brand and domain

Next layer — brand and domain. A public app must be tied to a credible owner. For an app that has an external backend/MCP, you are expected to:

Verify your domain (Domain Verification). You add a TXT record to your domain’s DNS, and the Store verifies that the backend truly belongs to you. Anonymous setups on a free ngrok URL either won’t pass into the public Store or will be marked as low trust.

Use a sensible name and logo. You can’t call yourself “ChatGPT Super Weather” or “Official OpenAI Something”—using “GPT / OpenAI / ChatGPT” at the beginning of the name or copying OpenAI’s brand style is restricted. Come up with your own name (GiftGenius is a good example) and visual style.

UI/UX: don’t break ChatGPT

Unlike the “old” plugins, an app can now render its own UI widget right in the chat. This brings many opportunities…and many ways to mess things up.

The Store has a simple idea: the widget should feel “native” relative to ChatGPT. Fonts, spacing, colors, behavior in dark/light theme and on mobile—all should look neat, without making it feel like you embedded an ad banner or an entire separate SPA into the chat.

The Store also dislikes UI that hijacks the chat: huge full-screen overlays, modal “subscribe now” dialogs, auto-scrolls, and other aggressive patterns. Your widget is a card/wizard/tool inside the conversation—not its own universe.

In essence, moderation looks at three things: whether you violate content policy, whether you are misleading (more on this closer to the end when we discuss the listing and manifest alignment), and whether you’re turning ChatGPT into an ad dump with poor UX. “Honesty” here means the alignment between what the app can actually do and what you claim in the description and UI.

3. How the Store views your app’s permissions

Another major axis of requirements is which rights you request from the user and external systems. Here the Store looks not only at security but also at how well those rights match the app’s stated value.

Now—the fun part for engineers: the permission model. In the context of the Apps SDK and MCP you have three main access levels.

For clarity, it’s convenient to depict it like this:

graph TD
    A[App manifest/config] --> B[Model capabilities]
    A --> C[OAuth scopes]
    A --> D[MCP tools & ACP]
    D --> E[User confirmation level]

Model capabilities are, strictly speaking, not “permissions” in the same sense as OAuth scopes or write tools, but rather a set of the model’s built-in abilities. Yet for security design, it’s convenient to treat them as the first access level that also needs to be minimized.

Level 1: model capabilities

This is what the model can do “by itself,” without calling your backend: web browsing, DALL‑E image generation, etc.

If you enable both browsing and MCP tools, the model may sometimes decide it’s easier to solve the task through web search rather than your specialized tool—especially if tool descriptions are vague or priorities aren’t set in the prompt. Therefore, if the app already talks to your API via MCP, it can make sense to either disable browsing or explicitly lock in the priority of MCP tools in the prompt.

So even at this level, you apply the principle of least privilege: turn off anything that isn’t needed for the app’s real value.

Level 2: OAuth scopes

If your app uses authentication (Module 10), you request scopes from an external provider: openid, email, profile, orders.read, orders.write, etc.

Here, minimalism is especially important:

  • If you just need to distinguish users, openid (an anonymous identifier) is usually enough, and email isn’t needed at all.
  • If email is indeed needed, it should be transparent in the UX and permission descriptions: “needed to send you receipts and order reminders,” not “just in case.”

We also try to do authorization “on demand”: let users try basic features without logging in, and request access only when they truly want, for example, “save a gift list to favorites” or “view order history.” This reduces friction and boosts conversion.

Example scope configuration for MCP tools (simplified):

// server/mcp/config/auth.ts
export const OAUTH_SCOPES = {
  basic: ["openid"],
  orders: ["openid", "orders.read"],
  checkout: ["openid", "orders.read", "orders.write"]
};

Level 3: MCP tools and “consequential” actions

The third level is your MCP tools and ACP/Instant Checkout. Each tool in the MCP server can be:

  • read‑only: get the exchange rate, pick gifts, view the catalog;
  • state‑changing (consequential): create an order, send an email, charge money.

For tools of the second type, the Store expects a stricter confirmation model. The idea is: not everything can be invoked “just like that.” In platform terms, this is usually expressed through the consequential: true flag and a confirmation policy (always_allow versus ask_user).

Example of registering an MCP tool with security schemes and the note that it’s a state‑changing action:

// server/mcp/tools/createOrder.ts
server.registerTool(
  "create_order",
  {
    title: "Create order",
    description: "Creates a new order in GiftGenius.",
    inputSchema: {
      type: "object",
      properties: {
        productId: { type: "string" },
        quantity: { type: "integer", minimum: 1 }
      },
      required: ["productId", "quantity"]
    },
    _meta: {
      securitySchemes: [{ type: "oauth2", scopes: ["orders.write"] }]
    },
    // pseudo-field: indicates this action changes state
    consequential: true
  },
  async ({ input, security }) => {
    // ... order creation logic
  }
);

The example of scopes and security schemes comes from the official MCP tools documentation, where tools can be either unauthenticated or protected by OAuth2.

From the Store’s perspective, this turns into clear text like “This app can create and manage orders in the GiftGenius shop,” and possibly a separate confirmation step.

4. Permissions through the eyes of the user and the moderator

To us engineers, an app is a manifest, an MCP server, and a bunch of TypeScript. To the Store, it’s a set of facts: what the app can do with user data and with the outside world.

You can think of it as the following table:

Access level Example for GiftGenius How the Store/user will see it
Model capabilities Browsing: off, DALL‑E: off “The app doesn’t go to the internet on its own, doesn’t generate media”
OAuth scopes openid, orders.read “Reads your orders in your GiftGenius account”
Read‑only MCP tools search_products, get_price_history “View catalog and prices”
Consequential MCP tools create_order, cancel_order “Create and cancel orders”

The key idea: each technical element should map to a human-understandable action. In the module plan this is stated explicitly: the technical MCP tool get_user_orders becomes “View the list of your orders in our shop” in the listing.

If you can’t explain a permission in one or two sentences, that’s a red flag. You may either be asking for too much, or have mixed several different jobs into one app.

5. The principle of minimally necessary permissions

In the usual backend world, the PoLP (Principle of Least Privilege) is often perceived as “yeah, we should somehow restrict DB roles—later.” In ChatGPT Apps it’s not “later”; it’s a criterion for getting into the Store and a factor in user conversion.

Key points:

  • The fewer rights the app requests, the higher the user’s baseline trust. A chat inside ChatGPT is a space where the user expects a certain level of privacy. An app that suddenly asks for access to the entire account, payments, and contacts looks suspicious.
  • The clearer and narrower the permissions, the easier it is for the reviewer. A moderator needs to quickly understand what the app does and how it aligns with policies and security best practices. Over‑permissioned apps are typical candidates for “on hold, request clarifications,” and sometimes a rejection.
  • The more minimal and just‑in‑time your access, the smoother the UX. The auth screen is a major point of friction. If the app provides a useful experience even before authorization (e.g., shows top gifts without tying to a user), the user is more likely to agree to extended capabilities later.

Therefore, “minimal permissions” in the Store are not only about security—they’re also about marketing and growth. Module 18 specifically emphasizes that minimal permissions are a competitive advantage, not a bureaucratic formality.

6. Examples: GiftGenius permissions before and after a “diet”

To keep this from being abstract theory, let’s take our hypothetical hero—GiftGenius. Imagine you designed it “to the max” and ended up with the following list of required capabilities:

  1. Read the product catalog and filter gifts.
  2. View the user’s order history.
  3. Create new orders and cancel existing ones.
  4. Save “favorite lists” in the user’s account.
  5. Send email notifications about discounts.

At the configuration level, this might look like this:

// server/mcp/config/permissions-naive.ts
export const PERMISSIONS_NAIVE = {
  capabilities: { webBrowsing: true, dalle: false },
  oauthScopes: ["openid", "email", "orders.read", "orders.write"],
  tools: {
    searchProducts: { consequential: false },
    getUserOrders: { consequential: false },
    createOrder: { consequential: true },
    cancelOrder: { consequential: true },
    saveFavoriteList: { consequential: true },
    sendDiscountEmail: { consequential: true }
  }
};

On paper, this set looks reasonable (“we’ll need all of this sooner or later”), but for the first Store release it’s overkill:

  • You don’t have to read order history right away. You can stick to a one‑off recommendation and a safe checkout via ACP/Instant Checkout, where the payment flow is controlled by the platform anyway.
  • Email notifications are a whole different story: they require storing email, explaining it in the Privacy Policy, and handling unsubscribes. For the GiftGenius MVP this is almost always excessive.

With the minimization principle in mind, you can assemble a minimal starting set of permissions:

// server/mcp/config/permissions-v1.ts
export const PERMISSIONS_V1 = {
  capabilities: { webBrowsing: false, dalle: false },
  oauthScopes: [], // no login, operate anonymously
  tools: {
    searchProducts: { consequential: false },
    createOrder: { consequential: true }
  }
};

In this version:

  • The app doesn’t poke into the user’s account, doesn’t read their history, and doesn’t send email.
  • All sensitive operations (order creation) go through ACP/Instant Checkout, where the user already sees the standard payment flow.

In the listing you can honestly state: “Picks gifts and creates orders in the GiftGenius shop. The app does not store your chat history and does not send email notifications.” That’s pleasant both for the user and the reviewer.

Later, once you have stable traffic and trust, you can release an update with additional permissions (order history, favorites) and update the listing and Privacy Policy accordingly.

7. How to describe permissions in the listing

The manifest and configuration are machine language. The moderator and user read a very different text: title, description, the “What this app can do” section, and links to Privacy/Terms.

Module 17 emphasizes the mapping: technical scopes and tools → human‑readable actions.

For GiftGenius v1, we could present it like this.

Technically:

  • Browsing: off
  • DALL‑E: off
  • MCP tools: search_products (read‑only), create_order (consequential)

In the listing:

  • “Picks gifts based on your description or parameters (gender, age, budget, interests).”
  • “Can create orders in the GiftGenius shop via secure checkout inside ChatGPT.”
  • “Does not request access to your email or order history, does not send notifications.”

If we later add OAuth login and orders.read, the description will honestly update:

  • “When you connect your GiftGenius account, it can view your past orders to provide more personalized recommendations.”

It is very important not to promise what the app doesn’t do and not to omit critical actions. The documentation compiled for Module 18 explicitly emphasizes: the information in the listing must exactly match the real behavior, especially for sensitive things like payments and PII.

8. How Store requirements relate to your architecture

It’s important to see that Store requirements don’t exist in a vacuum. These are not “just another form from marketing.” The Store essentially checks what you’ve already been doing in the security and production modules:

  • If you configured OAuth and properly set up .well-known endpoints and token verification, it would be odd if the app suddenly asked the user for half the internet through broad scopes. Such an app will easily fail review as over‑permissioned.
  • If you implemented a sound retention policy and PII scrubbing, it will be easier to write a truthful Privacy Policy and pass the check. The Store and users can follow the link and compare your promises to your real processes.
  • If you tuned the stability of the MCP server, logs, and metrics (modules on observability and SLO), reviewers will have fewer questions about performance and tool errors.

Minimal permissions neatly complement this picture: you’re not only safe and stable, but also “modest” in your requests for user data.

9. Mini‑practice during the lecture

So we don’t just theorize, break down your current app (or GiftGenius) step by step right now.

First, list all real actions the app can do. For example: “pick gifts,” “create an order,” “show history,” “save to favorites,” “send an email to colleagues.” It’s best to do this as plain text without thinking about technical details yet.

Next, for each action answer for yourself: “What user data does this touch?” and “Does it change state in an external system?” This will automatically split actions into read‑only and consequential.

After that, map the actions to permission levels: where only model capabilities are needed, where OAuth scopes are needed, and where MCP tools with the consequential: true flag and possibly user confirmation are required.

Now play “scissors”: what can be cut from the first release without killing the core value? It often turns out that without history, favorites, and email notifications the app still does its main job. Which means those permissions can be left for version 1.1 or 2.0.

10. Common mistakes with Store requirements and permissions

Mistake #1: “We’ll make a super app that does everything, and the Store will sort it out.”
A developer describes the app as a universal assistant (“I’ll help with finance, medicine, law, and shopping”), connects a dozen MCP tools, and asks for maximum rights. Such an app simultaneously dives into sensitive domains (medicine/finance/law), requests a lot of data, and violates the “one job per app” principle. The result is predictable: moderation will ask many questions or simply reject it. It’s better to make several narrow apps with clear permissions.

Mistake #2: Over‑permissioned auth “just in case.”
A classic: the app asks for email, profile, orders.read, orders.write, billing.read, even though it only needs to “pick a gift based on a description.” From the user’s standpoint this looks like a greedy data collector, and from the Store’s standpoint like a risky app. The Apps security docs explicitly cite this as a bad practice.

Mistake #3: Manifest and listing mismatch.
Your manifest has create_order, cancel_order, and access to payment operations, while your description only says “recommends gifts.” Sooner or later a reviewer or user will notice that the app can do more than stated. This undermines trust and can lead to removal from the Store.

Mistake #4: Trying to hide sensitive actions behind “harmless” UI.
For example, you render a “Save list” button in the widget that actually emails everyone in the department or creates tasks in another system, without explaining this in the permissions. The Store dislikes surprises. Developer guidelines state clearly: the app should do exactly what it promises, with no hidden behavior.

Mistake #5: Asking for login “on entry” when you could do without it.
The app launches—and immediately asks to connect an account and grant access to everything, or else “it won’t work,” even though half the scenarios could be implemented anonymously. This hurts conversion and creates the impression you’re rushing to collect data rather than provide value. It’s much better to first show that the app is genuinely useful and only then explain why additional rights are needed.

Mistake #6: Ignoring the organizational context.
Sometimes a developer builds an app “for everyone” when it’s essentially an internal corporate tool. As a result, they bring very specific permissions (internal CRMs, private employee data) into the Store that are hard to explain to a broad audience. In such cases, an org‑only mode and internal review would have been the right target—not the public Store.

1
Task
ChatGPT Apps, level 18, lesson 0
Locked
“Store readiness” banner by domain and HTTPS
“Store readiness” banner by domain and HTTPS
1
Task
ChatGPT Apps, level 18, lesson 0
Locked
Human-friendly permissions description generator for the Store listing
Human-friendly permissions description generator for the Store listing
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION