Fictura
Outgoing

MCP server

Connect Claude, ChatGPT, Cursor, VS Code, Codex or any MCP client to your Fictura app — it reads your live paywalls, funnel, experiments, errors and docs, then writes integration code against your real setup instead of guessing.

An AI coding agent that has never seen your dashboard will invent a placement name, hardcode a price, and leave YOUR_API_KEY in the file. Connect it to this server and it reads the real thing first.

The server lives at https://mcp.fictura.co/mcp and speaks Streamable HTTP. https://api.fictura.co/mcp is the same server, so a config that already names it keeps working.

What an agent can change

It reads by default. An agent can see your revenue and your error stacks; it cannot make a paywall live, start an experiment, or change a setting. A handful of tools write, each one named here, each one narrow, and each one behind something you turn on:

  • Drafts. create_paywall_draft, save_paywall_doc, create_onboarding_draft and save_onboarding_doc create and edit draft paywalls and onboarding flows. A live one is refused. Every save is a new version, noted mcp: … in History, so you can roll it back. Going live stays a click in the dashboard.
  • The editor you have open. editor_set_doc puts a document on a Studio canvas after pair_editor connects to it with the 6-character code under Pair an agent. Nothing is saved: you see the change, can undo it, and save it yourself. unpair_editor ends the pairing.
  • Error issues. update_error_issue resolves, ignores, reopens, assigns, prioritises or adds a note to an issue — only when Issues settings → Let MCP keys change issues is on for the app.
  • AppsFlyer import. start_appsflyer_import starts the same import as the Start button under Setup → Import data, and connect_appsflyer connects AppsFlyer with its API token. Both need an MCP key minted with Can connect and import data. connect_appsflyer is never offered to a signed-in or keyless connection, because it takes a credential as an argument — connect AppsFlyer in the dashboard instead.

The draft and editor tools need a connection that can edit: an MCP key minted with Can edit drafts, or a sign-in where you chose read and edit drafts. A read-only connection still lists them, and calling one answers with how to get that access.

Two ways to authenticate

Data tools need one of these. The documentation tools need neither.

Sign in with Fictura

Add the URL with no headers. The first time the agent calls a data tool, your client shows a Sign in or Connect prompt. Log in to Fictura, pick one app, choose the access — read only, or read and edit drafts — and approve.

One sign-in connects one app. To switch apps, remove the connection, add it again, and pick the other app when you sign in. This is the way in for clients a person is sitting in front of: claude.ai, Claude Desktop, the Claude mobile apps, ChatGPT, Claude Code, Codex, Cursor and VS Code.

MCP key

Dashboard → Setup → Coding agents (MCP) → create a key. It is shown once, stored only as a hash, and revoking it takes effect on the next request. Send it either way:

Authorization: Bearer fk_mcp_YOUR_KEY
x-api-key: fk_mcp_YOUR_KEY

The key names the app, so there is no app id anywhere in the setup — and no way for one key to read another app's data. A key reads only, unless you tick Can edit drafts (it then starts fk_mcpw_) or Can connect and import data when you make it. Neither can be added to a key later.

Keys suit everything without a person to click Sign in: Cursor, VS Code, Windsurf, Codex and Claude Code configs you share or script, CI, and the OpenAI Responses API and Agents SDK.

Either way, the app needs an active Fictura subscription and the AI plan (or its own Anthropic key) before its data answers over MCP.

Connect

Both buttons add the URL only, so the editor asks you to sign in the first time the agent needs your data. To use a key in VS Code instead, install with a key: VS Code asks for it once and keeps it in its secret storage.

The dashboard's Coding agents (MCP) card has the same buttons, and an Add to Cursor with your new key already in it.

Claude (claude.ai, Desktop and mobile)

  1. Settings → Connectors → Add custom connector.
  2. Name it Fictura and paste https://mcp.fictura.co/mcp as the URL. Leave the advanced settings empty.
  3. Press Connect when Claude asks, and sign in.

Claude Desktop can also take a key through the stdio bridge.

ChatGPT

  1. Open Plugins, press + and choose Add custom MCP server.
  2. Name it Fictura and enter https://mcp.fictura.co/mcp under Connection.
  3. Choose OAuth for authentication, accept the warning and create it. ChatGPT sends you to Fictura to sign in.

ChatGPT connects by signing in only; it does not send custom headers, so there is no key option here.

Claude Code

claude mcp add --transport http fictura https://mcp.fictura.co/mcp

Then run /mcp inside Claude Code, pick fictura and sign in.

Add --scope user to have it in every project, or --scope project to share it through .mcp.json.

Codex

codex mcp add fictura --url https://mcp.fictura.co/mcp

Codex sees that the server takes sign-in and opens the browser. If it does not, run codex mcp login fictura.

Cursor

~/.cursor/mcp.json:

{
  "mcpServers": {
    "fictura": {
      "url": "https://mcp.fictura.co/mcp"
    }
  }
}

Cursor asks you to sign in the first time the agent needs your data.

VS Code

.vscode/mcp.json:

{
  "servers": {
    "fictura": {
      "type": "http",
      "url": "https://mcp.fictura.co/mcp"
    }
  }
}

Windsurf

Windsurf connects with a key:

{
  "mcpServers": {
    "fictura": {
      "serverUrl": "https://mcp.fictura.co/mcp",
      "headers": { "Authorization": "Bearer fk_mcp_YOUR_KEY" }
    }
  }
}

Claude Desktop with a key

The custom connector above is the simplest route. To connect Claude Desktop with a key instead, use the @fictura/mcp stdio bridge. In claude_desktop_config.json:

{
  "mcpServers": {
    "fictura": {
      "command": "npx",
      "args": ["-y", "@fictura/mcp"],
      "env": { "FICTURA_MCP_KEY": "fk_mcp_YOUR_KEY" }
    }
  }
}

The bridge defines no tools of its own — it only translates stdio to HTTP, so it never falls behind the server. FICTURA_API_BASE points it somewhere other than production.

OpenAI Responses API and Agents SDK

Your code is the client here, so it sends a key. In the Responses API, Fictura is an mcp tool:

import os
from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-6-astra",
    tools=[
        {
            "type": "mcp",
            "server_label": "fictura",
            "server_url": "https://mcp.fictura.co/mcp",
            "headers": {"Authorization": f"Bearer {os.environ['FICTURA_MCP_KEY']}"},
            "require_approval": "never",
        }
    ],
    input="Which paywall is live at each placement, and how is trial-to-paid trending?",
)
print(response.output_text)

require_approval: "never" suits a read-only key. With a key that can edit, leave the default ("always") or list only the read tools under {"never": {"tool_names": [...]}}.

In the Agents SDK, connect over Streamable HTTP:

import asyncio
import os
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp

async def main():
    async with MCPServerStreamableHttp(
        name="fictura",
        params={
            "url": "https://mcp.fictura.co/mcp",
            "headers": {"Authorization": f"Bearer {os.environ['FICTURA_MCP_KEY']}"},
        },
    ) as fictura:
        agent = Agent(name="Growth analyst", mcp_servers=[fictura])
        result = await Runner.run(agent, "Why did trial-to-paid drop this week?")
        print(result.final_output)

asyncio.run(main())

Documentation works without signing in

search_docs, read_doc and list_docs answer whether or not you have authenticated, so an agent can learn how Fictura works before anyone has signed up. Every other tool is listed too, so an agent can see what signing in would unlock; calling one without credentials is what makes your client ask you to sign in.

The docs an agent reads are the same pages you are reading, regenerated on every change to the product — so it is never working from a stale copy of the API.

Tools

Access says what a tool needs: none runs before sign-in, read works on any connection to the app, edit needs a connection that can edit drafts.

Documentation

ToolWhat it answersAccess
search_docsFull-text search across these docs, grouped by page.none
list_docsEvery page path by section, with a one-line description.none
read_docThe full Markdown of one page; a wrong path returns the closest real ones.none

Setup and integration

ToolWhat it answersAccess
get_app_setupAPI base, publishable SDK key, RevenueCat webhook URL, bundle id. The one to call before generating any integration code.read
get_setup_statusSetup checklist, every feature toggle, connected integrations.read
get_link_infrastructureDeep-link setup: Universal Links / App Links association, branded domains, web-to-app banner, ESP.read
get_push_configRegistered-device counts per cohort and whether OneSignal is configured.read
list_store_productsApp Store Connect subscriptions, optionally with Apple's price ladder.read

Analytics and users

ToolWhat it answersAccess
get_analyticsOverview, conversion funnel, cohort compare, or the split by store and country.read
get_tracking_planEvery event the app sends, with volume, last-seen and kill-switch state.read
list_users · get_userThe user roster, and one user's stitched identity, events and churn state.read
check_entitlementWhether a given user is subscribed, unified across Stripe, RevenueCat, App Store and Play.read

Paywalls, onboarding and experiments

ToolWhat it answersAccess
list_surfacesPaywalls, onboarding flows, what is live per placement, and the valid placement names.read
get_surfaceOne paywall or onboarding config, with version history.read
list_templatesThe built-in paywall and onboarding starter templates.read
list_experiments · get_experimentExperiments with full Bayesian results.read

Studio documents

ToolWhat it doesAccess
get_paywall_doc · get_onboarding_docThe JSON document a Studio paywall or builder onboarding flow is made of, with its status and version.read
validate_paywall_doc · validate_onboarding_docChecks a document with the same parser a save uses. Changes nothing.read
create_paywall_draftWrites. A new draft paywall from a Studio template or a document. Never made live.edit
save_paywall_docWrites. Replaces a draft paywall's document as a new version. Refused for a live paywall.edit
create_onboarding_draftWrites. A new draft onboarding flow from a document. Never made live.edit
save_onboarding_docWrites. Replaces a draft flow's document as a new version. Refused for a live flow.edit

Studio editor

ToolWhat it doesAccess
pair_editorConnects to a Studio editor someone has open, with the code under Pair an agent. A code works once, for 10 minutes; a pairing lasts 24 hours.read
editor_get_docThe document exactly as it is on screen, unsaved changes included, with the selected screen and node.read
editor_set_docWrites to the screen, not the database. Puts a document on the canvas; the person saves it.edit
unpair_editorEnds the pairing.read

Attribution and acquisition

ToolWhat it answersAccess
get_attribution_reportAttribution summary, KPIs by source/campaign/link/day, or raw installs.read
get_acquisition_reportROAS, raw ad spend, or tracking links.read
list_campaignsLive campaigns from a connected ad platform.read
list_creatorsCreator records and their 28-day funnel.read
get_store_analyticsApp Store and Google Play numbers synced from Appfigures: downloads, store revenue, ratings, reviews, chart positions.read
get_appsflyer_import_statusWhether AppsFlyer is connected, the latest import run, and whether one ever finished.read
get_appsflyer_attribution_summaryWhat the AppsFlyer import brought in, by media source and campaign.read
start_appsflyer_importWrites. Starts or resumes the AppsFlyer import in the background.key with Can connect and import data
connect_appsflyerWrites. Connects AppsFlyer with its API token. Listed only to keys that can import.key with Can connect and import data

Errors and tracing

ToolWhat it answersAccess
list_error_issues · search_error_issuesThe error feed, or issues matching text, platform or release.read
get_error_issueOne issue with its recent occurrences, stack traces and daily counts.read
get_error_issue_briefOne issue condensed for fixing it, with a ready-to-use fix prompt.read
get_traceEvery span of one distributed trace, as a tree.read
update_error_issueWrites. Resolve, ignore, reopen, assign, prioritise or add a note.read, with Let MCP keys change issues on

There is also an integrate_fictura_sdk prompt that walks an agent through a correct first integration.

What it deliberately cannot see

Push tokens and individual recipients (they identify people, and counts answer the same question), your OneSignal REST key, integration credentials such as the AppsFlyer token, and your Supabase token. App Builder projects are out of scope for now — that is Fictura's own agent, and pointing a second one at its working tree solves nothing.

Why this beats pasting docs into the chat

Three integration mistakes are near-universal, and all three are things only your account knows:

  • Placement names. A <GrowthPaywall placement="..."> naming a placement that does not exist renders nothing and reports no error. list_surfaces returns the names that are actually configured, alongside the ones your app has been observed sending — which is how you spot a typo you already shipped.
  • Deep links. Universal Links and App Links both fail silently: a malformed team id produces an association file that parses, associates nothing, and shows no error anywhere, so links keep opening the browser. get_link_infrastructure returns ios_ready and android_ready rather than making an agent guess.
  • Prices. Paywall configs never contain price strings — prices resolve at render from the store. An agent that has read get_surface sees that; one working from memory writes a literal and risks a rejected build.

Troubleshooting

401 Invalid or revoked Fictura credentials — something was sent and it does not work. A sign-in that expired or was revoked: sign in again (in Claude Code, /mcp → fictura). A key: it was revoked or pasted wrong, so mint a new one under Setup. With nothing sent at all, a data tool also answers 401 — that is the signal your client uses to show Sign in. A client that cannot sign in needs a key.

403 This Fictura connection is no longer linked to an app you can access — the sign-in still works but its app does not: it was archived, the grant was revoked, or you were removed from the app. Remove the connection, add it again, and pick an app when you sign in.

402 — the app is not allowed MCP access yet. It either needs an active Fictura subscription (dashboard → Billing), or it is on the standard plan and needs the AI plan or its own Anthropic key. If the message says the month's AI allowance is used up, adding your own Anthropic key keeps it going.

406 Not Acceptable — the client sent no Accept header, or only one of the two required values. Streamable HTTP needs Accept: application/json, text/event-stream. Every supported client does this for you; it usually shows up when testing with curl.

405 Method Not Allowed — the endpoint accepts POST only. There is no session to resume and no server-initiated stream, so GET and DELETE have nothing to do.

An MCP key rejected by the Links API — that is deliberate. fk_mcp_… keys authenticate /mcp and nothing else; the Links API takes fk_live_…. Neither surface accepts the other's key, so a key handed to an agent can only do what this page lists. See Authentication.

On this page