> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apostra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a white-label ChatGPT app

> Connect the v3 MCP endpoint in ChatGPT, record a review demo, and prepare a customer-branded submission.

Use this guide to test an Apostra-powered, customer-branded app in ChatGPT
before preparing its public submission. ChatGPT currently labels this
developer-mode connection a **Plugin**.

<Warning>
  A developer-mode connection is private test configuration. Its name, icon, and
  description do not publish an app, change Apostra MCP server's
  identity, or automatically carry into another customer's submission.
</Warning>

## Before you start

You need:

* a ChatGPT account or workspace that permits Developer mode;
* an Apostra account with access to a buyer or seller account enrolled in
  the v3 preview that you will demonstrate;
* an owner-approved app name and short description; and
* a square PNG icon, ideally 256 x 256 pixels and no larger than the limit
  displayed by ChatGPT. The current form accepts at most 10 KB.

Use a review or demo account with representative data. Do not record production
credentials, access tokens, private customer data, or real campaign spend.

## 1. Add your plugin in Developer mode

1. In ChatGPT, open **Settings** → **Security and login**.
2. Turn on **Developer mode**. Availability depends on the ChatGPT account and
   workspace policy.
3. Open the [ChatGPT Plugins page](https://chatgpt.com/plugins) and select the
   plus button.
4. Complete **New Plugin** with these values:

| Field | What to enter |
| - | - |
| Icon | The app owner's approved 256 x 256 directory icon from the private-label handoff. |
| Name | The owner-approved customer-facing app name from the handoff. |
| Description | The owner-approved short description from the handoff. |
| Connection | Select **Server URL**. |
| Server URL | The exact working private-label MCP URL from the handoff. Do not substitute the shared Apostra `/mcp/v3` endpoint. |
| Authentication | Select **OAuth**. ChatGPT discovers the OAuth settings from the private-label server. |

5. Review the custom-server warning and select **I understand and want to
   continue** only if the endpoint exactly matches your private-label handoff.
6. Select **Create** and complete the app's sign-in flow with a reviewer whose
   home Apostra account is the account assigned to this private-label app.
   The hostname is account-bound and refuses a different active account.
7. Review the tools and metadata ChatGPT discovers from the server.

If ChatGPT cannot create the connection, copy the private-label URL from the
handoff again, check that **OAuth** is selected, and confirm that your workspace
allows custom MCP servers. Do not edit the URL path or fall back to the shared
Apostra endpoint. The [v3 authentication guide](/v3/authentication) explains
which Apostra account and permissions the login grants.

If **Scan Tools** reports `Access token is not valid for this protected
resource` after changing the MCP Server URL, disconnect the saved authorization
and reconnect before scanning again. ChatGPT discovers the OAuth configuration
from the server; no advanced-field overrides are required.

## 2. Verify the connection

Start a new chat, enable the plugin from the tools menu, and ask:

```text theme={null}
Call get_status. Tell me the active account, whether it is a buyer or seller,
its readiness, and which other accounts I can reach.
```

Confirm that the result names the assigned account before demonstrating any
workflow. If it does not, reconnect with the grant-bound reviewer credential
for that account's System advertiser; do not switch a private-label app into
another account. Then test
the exact prompts you plan to give reviewers:

* a read that returns useful account data;
* a representative write that shows the expected confirmation before it runs;
* a follow-up that reuses an identifier from the prior result; and
* an unsupported or unsafe request that produces the intended clarification,
  refusal, or safe fallback.

Use demo fixtures for writes and stop before creating real spend. If you change
tool metadata or deploy a server update, refresh the connection and rerun the
tests in a new conversation.

## 3. Record the review video

Record one clean end-to-end session while the developer-mode plugin is enabled.
The recording should show:

1. the app name and icon in ChatGPT;
2. the plugin selected for a new conversation;
3. `get_status` confirming the review account;
4. at least one representative read workflow;
5. the confirmation and result for a safe demo write, when the app supports
   writes; and
6. the expected behavior for one negative test.

Keep the browser URL, app identity, prompts, confirmations, and final results
legible. Hide password-manager overlays, credentials, OAuth tokens, personal
data, internal admin screens, and unrelated browser tabs. Store the recording
in the customer's approved file-sharing system; do not commit video files to a
source repository.

The video is part of the submission handoff and a useful reproduction artifact.
OpenAI's portal requirements can change, so check the live form to determine
whether to upload the recording, link it, or retain it for reviewer follow-up.

For reference, watch the
[example review video created for Apostra app](https://youtu.be/I82M1i11NFs).

## 4. Prepare the public submission

Developer-mode branding is not a public listing. The intended handoff is one
app-specific submission package rather than a request to assemble each upload
by hand. An account admin prepares it on the **Listing** page's ChatGPT
destination (see [Create a white-label ChatGPT app](/v2/setup/chatgpt-app-setup)
for the full walkthrough; the retired Account Settings "Discovery &
distribution" tab, and later the retired "Branding & distribution" page, held
these same controls before AI-7434 and AI-8891 moved them here):

1. Get **Public distribution** — the Listing page's fourth step — live: add
   the Distribution package, then point your domain at Apostra with one
   CNAME. (An app that already runs on its own active per-app hostname keeps its
   ChatGPT destination — existing reviewer grant, token, and package — without
   this step; requesting a new reviewer grant still needs it.) Apostra checks it for you; there is no separate tenant-specific
   TXT record or dedicated ChatGPT hostname to configure. Apostra
   first-party app uses its platform-managed hostname and skips this step
   entirely.
2. Review **Brand identity** at the top of the page. It identifies whether the
   storefront resolves a self-hosted `brand.json`, an AAO-managed identity, a
   community profile, or no reusable identity. A usable resolved logo
   automatically generates the 256 × 256 directory PNG and 48 × 48 composer
   PNG, but this is optional. You can instead upload both channel-specific
   PNGs from the ChatGPT destination. If no identity resolves, you can use the
   AAO builder to self-host or request AAO-managed hosting, or skip that step
   and upload the app icons directly. The directory category is set to Business by Apostra for new
   registrations (an existing registration keeps its saved category; the
   value appears in the approval preview); other one-time portal choices are completed in
   OpenAI rather than duplicated here.
3. In the ChatGPT destination, choose **Request access** once the listing is
   Public. (An app that already runs on its own active per-app hostname keeps
   an existing grant, its token, and its package without Public distribution;
   a new grant still requires it.) The action requires the storefront's Listing + Distribution
   package to include customer-branded AdCP, customer-branded ChatGPT app,
   organization advertiser lifecycle, and public listing distribution
   entitlements. Apostra records a bounded review request owned by the
   current account; the destination shows its requested, provisioning,
   active, failed, revoked, or expired state. Operator activation creates a
   no-spend System advertiser in this storefront and issues one
   advertiser-scoped, read-only buyer credential. When the grant is active,
   choose **Open reviewer login** to view and copy its email and password.
   Account admins can reopen the same login while the grant remains active;
   Apostra does not put it in the submission ZIP or expose it to the
   model. The login is shared across OpenAI, Anthropic, and Microsoft remote
   MCP directory reviews for this listing. Choose **Revoke reviewer access**
   as soon as review ends. If status is temporarily unavailable, the
   destination disables both actions until a refresh can safely determine the
   current grant.
4. Paste the OpenAI verification token in the same ChatGPT destination —
   this proves ownership of the storefront's own public listing domain, the
   same one verified in Public distribution, not a second hostname. After you publish
   the token, the destination checks (`probe_discovery_openai_challenge`)
   only that the address returns that exact token; OpenAI performs domain verification
   separately.
5. Review the values OpenAI will see, then select **Prepare download**. If the
   listing changed while the page was open, review the refreshed values and
   prepare it again. When the package is ready, select **Download package** to
   start the browser download directly from that second click.

The page hydrates this control from `get_chatgpt_app_config`.
`reviewerSandboxAvailable` is `true` only when the current Public listing and
complete entitlement bundle permit a new request. `reviewerSandboxGrantStatus`
is `loaded` when both reviewer eligibility and the existing account-owned grant
lookup succeeded. It is `unavailable` when either lookup cannot be read safely,
so the page offers a retry instead of treating an outage as a missing
entitlement. An existing grant remains visible and revocable if later
configuration changes make a new request ineligible.

The download, `openai-plugin-handoff.zip`, contains:

* the plugin ZIP to upload to OpenAI, named `<plugin-name>-<version>.zip`;
* `readiness-report.json`, which records a stable snapshot ID, every portal
  field, its source, readiness, blockers, and regeneration rules; and
* one consolidated `README.md` with the complete field map, the review cases,
  locale guidance, release copy, screenshot guidance, and video storyboard.

Upload the plugin ZIP itself in OpenAI's Plugins dashboard, unchanged. Do not
upload the outer handoff ZIP, and do not unpack or edit the plugin ZIP. It uses
OpenAI's portable plugin layout:

* `plugin.json` carries the listing (display name, short and long description,
  developer name, category, website, support, privacy, and Terms URLs), up to
  three starter prompts, five positive and three negative review cases, and
  release notes. OpenAI imports these fields when you upload the ZIP and shows
  the review cases read-only in **Review details**.
* `mcp.json` declares the app's production MCP URL.
* `assets/` holds the selected 256 × 256 logo and 48 × 48 composer icon,
  whether generated from resolved brand identity or uploaded for this app.
* `skills/` holds the canonical skills for the account. Buyer apps include
  the account-readiness, campaign-setup, event-source setup, and
  campaign-management skills; seller apps include no skills.

Every download carries a higher plugin version than the one before it, so you
can upload a fresh download to the same plugin in OpenAI as an update. If your
app was published through OpenAI's previous submission form, OpenAI requires
the plugin ZIP to declare the identifier it assigned (`app-…`); if an upload
reports "Plugin name must match the existing plugin", send that identifier to
Apostra support before regenerating.
Apostra does not infer your publisher identity, so the plugin ZIP leaves the
developer name for you to enter in OpenAI's dashboard. Use the name of the
verified individual or business identity of the OpenAI organization that
publishes the plugin; OpenAI displays that verified identity in the
directory.

OpenAI caps plugin display names and short descriptions at 30 characters.
Apostra never truncates the display name: if the storefront name is longer,
the readiness report blocks the package until you shorten it.

OpenAI scans the production MCP server's tools when you connect it under
**MCPs**. Tool annotations come from that live scan, not from the ZIP. Sign in
with the reviewer login, not a staff account, so the scan sees the tools the
reviewer will use. After publication, OpenAI rescans the server daily, and
eligible tool changes go live without a new upload. A change to the listing,
icons, review cases, or skills needs a fresh download, a new upload, and a new
review.

A storefront with no logo in its resolved brand identity can still download
the package after both app-specific PNGs are uploaded from the **ChatGPT
destination**. Upload the 256 × 256 directory icon and 48 × 48 composer icon
together; an incomplete pair is rejected. To use automatic icons instead, add
a logo at the system that manages the identity, refresh the page, choose
**Use automatic icons**, and regenerate the bundle. The source choice
persists across later saves; switching to automatic does not copy generated
PNG bytes into the manual override fields. Legacy API integrations may
continue updating one existing icon at a time, while new manual selections
must provide the pair. Apostra can read a self-hosted `brand.json`, but
cannot edit it.

The verification token is public at the challenge URL and visible on the
admin-only Listing page, but deliberately excluded from the ZIP, logs, and
reviewer handoff. Never reuse another customer's bundle or replace its exact
private-label MCP URL with the shared Apostra endpoint.

The publisher still completes the requirements that cannot safely be generated:

* select the verified business or individual identity that will publish the
  app;
* confirm Apps Management write access for each submitter;
* confirm the domain-verification challenge published from the Branding &
  distribution Page before pressing Verify in OpenAI;
* confirm that the account-owned reviewer request created its dedicated,
  least-privileged no-spend System advertiser. Open the reusable reviewer
  login from the destination, paste it only into the directory's protected
  test-access field, ensure it works without MFA, SMS, email confirmation, or
  private-network access, and revoke the grant when review ends;
* approve country availability, policy attestations, and the generated listing
  and release copy; and
* approve the final review video and its sharing location.

Follow [OpenAI's current submission guide](https://developers.openai.com/plugins/deploy/submission)
when completing the portal. Resolve every finding in **Metadata & Skills** and
**MCPs**, rescan the production server immediately before submission, and
enter the demo recording URL, reviewer login, country availability, and
commerce answer in **Review details**.

## 5. Manage review and publication

Submitting starts OpenAI's review; it does not publish the app. Keep a release
record in your approved system with the bundle's snapshot ID, source revision,
generation and submission times, OpenAI draft or version reference, publisher
owner, and current review state. Keep the exact outer ZIP in approved,
access-controlled release storage. Do not put reviewer credentials, OAuth
tokens, domain-verification tokens, or private customer data in that record,
the ZIP, or a support ticket, and do not attach the ZIP to an unrestricted
ticket.

While review is active, keep the storefront-owned System advertiser, its
grant-bound read-only reviewer login, synthetic no-spend fixtures,
production MCP endpoint, domain challenge, tool metadata, and scanned skills
available. Send Apostra support any reviewer feedback verbatim with the
snapshot ID. Do not regenerate or hand-edit the submitted files unless the
feedback requires a new version.

After approval, test the approved snapshot once more, then open the approved
package version in OpenAI and select **Publish plugin**. Verify directory discovery, installation, OAuth,
`get_status`, and one safe workflow, and monitor the launch for 24 hours. Revoke
the grant-bound reviewer credential when OpenAI no longer needs it.

After rejection, preserve the exact feedback and snapshot ID. Correct the
canonical configuration or Apostra source, download a fresh package, upload
its plugin ZIP to the same plugin, rescan tools, rerun all five positive and
three negative cases, and submit the new package version. Only one review can
be active per plugin. Published metadata and packaged skills are reviewed
snapshots; a skill update reaches the published plugin only through a new
upload, review, and publish.

## Get conversational setup help

Give an MCP-capable agent the public, versioned
[Publish an OpenAI App skill](https://api.apostra.com/skills/publish-an-openai-app/SKILL.md).
Authenticated Seller Accounts also advertise it through the MCP Skills
extension, so Murph and compatible clients can load the same workflow without
copying this guide into a prompt.

Distribution uses the existing V3 noun surface rather than adding
provider-specific tools:

* `get({ kind: "distribution" })` returns the configured public hostname, CNAME
  target, ownership record, hostname status, MCP URL, and OpenAI challenge
  status. An `active` customer CNAME is the server-owned verification result;
  the agent does not claim to test an OpenAI submission.
* The same read returns a bounded `channels.chatgpt` summary: whether the
  listing is ready to submit, how many blocking items remain, this channel's
  own hostname and verification status, whether the submission bundle is
  current and the listing approved, and one next-step line. It never claims
  the app is live in ChatGPT — OpenAI's review and publish decision happens
  entirely on their portal — so it reports platform-side readiness only.
* `save_seller({ distribution: { openaiChallengeToken: "..." } })` publishes
  the portal-provided challenge token. Replacing or removing a current token
  requires the corresponding confirmation field.

Both operations are Seller-account-admin only. Reviewer access, portal scans,
screenshots, and video remain manual work described by the submission package.

For an end-to-end conversational setup, connect the V2 Storefront MCP endpoint
and follow its Distribution skill section. It exposes the same guarded
operations as the Listing page: update the shared listing, configure and
publish Discovery, configure ChatGPT icons and the domain-ownership challenge,
request reviewer access, and obtain a fresh signed OpenAI package URL. The
agent makes one mutation per turn and stops for explicit owner confirmation at
each consequential boundary. OpenAI portal scans, screenshots, attestations,
submission, and publication remain manual.

## Apostra's own plugin

Apostra publishes its own plugin through the same download, from its
first-party distribution account. Its package declares the MCP endpoint on the
first-party listing's hostname and publishes under Scope3's verified OpenAI
identity. Installation starts OAuth; API keys and provider credentials never
belong in the package or prompts.

## Understand the white-label boundary

Apostra does not infer ChatGPT branding or publisher details from an AdCP
partner registration, seller profile, or MCP connection. In particular:

* adding a partner AdCP endpoint does not turn it into a ChatGPT MCP endpoint;
* a customer's logo and listing copy must be provided in that customer's
  developer-mode configuration; the public submission uses the automatic or
  app-specific icon pair selected on the Listing page;
* the verified publisher identity, public policies, and support details must
  match the organization submitting the app. When the app uses Apostra,
  the customer's privacy policy must also disclose the relevant Apostra
  data handling; and
* a customer-owned MCP host requires its own production endpoint, OAuth
  metadata, tools, annotations, UI resources, security review, and submission.

Do not use the shared `https://api.apostra.com/mcp/v3` endpoint for a
private-label Developer-mode connection or public submission. OpenAI verifies
one exact token at the host-level `/.well-known/openai-apps-challenge` path;
changing only the MCP URL path cannot separate multiple app challenges on the
same host.

A customer-owned public app therefore needs either a dedicated,
customer-controlled hostname that serves its MCP, OAuth, and verification
challenge, or an explicit arrangement approved by both Apostra and OpenAI.
Control of an Apostra account does not grant control of the
`api.apostra.com` domain.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.