Magnus
Analytics for app publishers
- Category
- Data & Analytics
- Primary Subcategory
- Product Analytics & Experimentation
Integration details
Description
Magnus is an analytics platform for mobile and web app publishers. It aggregates marketing spend from ad networks, subscription and in-app revenue, ad-network monetization, payment-provider outcomes and first-party product-analytics events into one place, so a publisher can ask how a campaign, an app or an experiment is actually performing. Over MCP the app exposes that data read-only. Three tools answer questions: marketing_timeline_report for acquisition and monetization performance (installs, spend, revenue, ROMI, forecasts), finances_risk_report and finances_risk_approval_rates for payment risk (approval rate, refunds, fraud alerts, disputes, chargebacks). A second group answers product-analytics questions from the publisher's own SDK events: event counts, funnels, retention, user-property segments and A/B experiment configuration. Everything else is a lookup helper that exists to feed those tools with real ids, filter values and metric definitions, because app ids, event names and metric semantics are account-specific and cannot be guessed. Access is scoped to the companies the signed-in user is an active member of, and that membership is re-checked on every single call, so the model can never reach another tenant's data. The OAuth scope the app requests, mcp:analytics.read, grants read access only: no tool in the server can create, update or delete anything.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- Product Analytics & Experimentation
- Secondary Subcategories
- None listed
- Brand
- Magnus
- Access
- Account required
- First tracked
- 2026-08-22
- Tool count
- 52
- Geography
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is listed.
ChatGPT Plugin Discovery Score
ChatGPT Plugin discovery is coming soon
ChatGPT can surface a Plugin when it matches a user's request.Your Plugin Discovery Score measures how often yours appears.
No spam. Unsubscribe any time.
What discovery looks like

Competing in ChatGPT Product Analytics & Experimentation
View Category52 tools agents can invoke
Changes the status, the daily budget or the bid of one Facebook ad set, in the ad account itself. This is a real change to live advertising: it takes effect immediately, it spends real money, and it cannot be undone from here. **Call expenses_optimus_entity_fetch first, every time.** It is where `company_ids` comes from — this tool needs the `write_company_id` it answered, exactly one id — and where you see what the field is set to now and the absolute range a new value has to land in. Values from an earlier call go stale. **One field per call.** The allowance is at most 3 changes to one field of one ad set and 20 per person per hour, and only changes that actually go through are counted — a refusal costs nothing but the round trip. Budget and bid both live here on Facebook, which is why this cell has all three fields — but a campaign using Advantage campaign budget holds the money itself, and then this ad set reports no budget of its own and the refusal says so and names the campaign. Change it there. **Units** — money is a decimal number in the ad account's own currency, in its main unit: 50 means $50.00. `status` is active or paused, this server's own two words; the network's own wording is never accepted. A change has to land between half and double the current value, and there is no override — outside that, make it in the ad manager. **Say what you are about to do before you do it.** One ad account is routinely shared by several companies, so the change is visible in the reports of every company that shares it, not only the one you passed. A change here also lands in the history that Magnus's own budget automation reads, which pauses that automation for this object for a while — intended, since a person's decision outranks a schedule, but worth saying out loud. **Ceilings and daily limits** — going up, a budget or bid may not be set above this server's ceiling: 5000 USD a day for a campaign budget, 1000 USD for an ad set or ad group budget, 200 USD for a bid, converted into the ad account's currency; the card's `bounds.max` already includes it. Decreasing and pausing are always allowed. Each person may also set at most 25000 USD a calendar day (UTC) in motion through this server — budget increases, the budgets of objects switched on and created budgets, summed — and the answer's `limits.per_day` says what is left and when it resets.
expenses_optimus_facebook_adgroup_update
Changes the status or the daily budget of one Facebook campaign, in the ad account itself. This is a real change to live advertising: it takes effect immediately, it spends real money, and it cannot be undone from here. **Call expenses_optimus_entity_fetch first, every time.** It is where `company_ids` comes from — this tool needs the `write_company_id` it answered, exactly one id — and where you find out whether the field can be written at all, what it is set to right now, and the absolute range a new value has to land in. Values from an earlier call go stale: someone else may have changed the campaign in the meantime. **One field per call.** Send status or budget, never both. To change two things, make two calls — and each of them counts against the allowance. **Units** — `budget` is a decimal number in the ad account's own currency, in its main unit: 50 means $50.00. The Meta Ads MCP server expresses the same number as an integer in the minor unit, so never carry a value between the two. `status` is active or paused, this server's own two words; the network's own wording is never accepted here. **Limits** — at most 3 changes to one field of one campaign and 20 changes in total per person per hour. Only changes that actually go through are counted, so a refused value costs nothing but the round trip. Read the card first anyway: it says what the current value is and what range a new one has to land in. **What a refusal means** — a change outside half-to-double the current value is refused with the range named, and there is no override argument: the honest answer is a smaller change or a visit to the ad manager. A campaign whose budget sits on its ad sets has no campaign budget to change, and the refusal says so and sends you to the ad sets, which is where it is. **Say what you are about to do before you do it.** One ad account is routinely shared by several companies, so a budget change is visible in the reports of every company that shares it, not only the one you passed. It also pauses Magnus's own budget automation for this campaign for a while, which is intended — a person's decision outranks the schedule — but the person should know it. **When NOT to use** — not for a Facebook ad set or ad (they have their own tools), not for Google Ads or TikTok, not to read anything, and not to rename: no network here writes a name. **Ceilings and daily limits** — going up, a budget or bid may not be set above this server's ceiling: 5000 USD a day for a campaign budget, 1000 USD for an ad set or ad group budget, 200 USD for a bid, converted into the ad account's currency; the card's `bounds.max` already includes it. Decreasing and pausing are always allowed. Each person may also set at most 25000 USD a calendar day (UTC) in motion through this server — budget increases, the budgets of objects switched on and created budgets, summed — and the answer's `limits.per_day` says what is left and when it resets.
expenses_optimus_facebook_campaign_update
Changes the status or the bid of one Google Ads ad group, in the ad account itself. This is a real change to live advertising: it takes effect immediately, it spends real money, and it cannot be undone from here. **Call expenses_optimus_entity_fetch first, every time.** It is where `company_ids` comes from — this tool needs the `write_company_id` it answered, exactly one id — and where you see what the field is set to now and the absolute range a new value has to land in. Values from an earlier call go stale. **One field per call.** The allowance is at most 3 changes to one field of one ad group and 20 per person per hour, and only changes that actually go through are counted — a refusal costs nothing but the round trip. There is no budget here, and that is Google's model rather than a gap: the money sits on the campaign, as a shared resource of its own. Asking for a budget on an ad group is refused with that reason instead of attempted, and the campaign is where to change it. **Units** — the bid is a decimal number in the ad account's own currency, in its main unit: 2.50 means $2.50, not two and a half million micros. `status` is active or paused, this server's own two words; the network's own wording is never accepted. A change has to land between half and double the current value, and there is no override. **Say what you are about to do before you do it.** One ad account is routinely shared by several companies, so the change is visible in the reports of every company that shares it, not only the one you passed. **Ceilings and daily limits** — going up, a budget or bid may not be set above this server's ceiling: 5000 USD a day for a campaign budget, 1000 USD for an ad set or ad group budget, 200 USD for a bid, converted into the ad account's currency; the card's `bounds.max` already includes it. Decreasing and pausing are always allowed. Each person may also set at most 25000 USD a calendar day (UTC) in motion through this server — budget increases, the budgets of objects switched on and created budgets, summed — and the answer's `limits.per_day` says what is left and when it resets.
expenses_optimus_google_adgroup_update
Changes the status, the daily budget or the bid of one Google Ads campaign, in the ad account itself. This is a real change to live advertising: it takes effect immediately, it spends real money, and it cannot be undone from here. **Call expenses_optimus_entity_fetch first, every time.** It is where `company_ids` comes from — this tool needs the `write_company_id` it answered, exactly one id — and where you see what the field is set to now and the absolute range a new value has to land in. Values from an earlier call go stale. **One field per call.** The allowance is at most 3 changes to one field of one campaign and 20 per person per hour, and only changes that actually go through are counted — a refusal costs nothing but the round trip. **The budget is a shared resource in Google Ads.** One budget can be attached to several campaigns, and changing it would move every one of them — so when it is actually shared the change is refused, and the refusal says how many other campaigns are on it. A budget this campaign has to itself changes normally. Google is also the one network with a bid at campaign level, and it is a target CPA either way — the same number, written into whichever of the two strategies the campaign runs. Only Target CPA and Maximize Conversions accept it at all; on any other bidding strategy the bid is not writable here and the attempt is refused. **Units** — money is a decimal number in the ad account's own currency, in its main unit: 50 means $50.00, not fifty micros. `status` is active or paused, this server's own two words; the network's own wording is never accepted. A change has to land between half and double the current value, and there is no override — outside that, make it in the ad manager. **Say what you are about to do before you do it.** One ad account is routinely shared by several companies, so the change is visible in the reports of every company that shares it, not only the one you passed. **Ceilings and daily limits** — going up, a budget or bid may not be set above this server's ceiling: 5000 USD a day for a campaign budget, 1000 USD for an ad set or ad group budget, 200 USD for a bid, converted into the ad account's currency; the card's `bounds.max` already includes it. Decreasing and pausing are always allowed. Each person may also set at most 25000 USD a calendar day (UTC) in motion through this server — budget increases, the budgets of objects switched on and created budgets, summed — and the answer's `limits.per_day` says what is left and when it resets.
expenses_optimus_google_campaign_update
Changes the status, the daily budget or the bid of one TikTok ad group, in the ad account itself. This is a real change to live advertising: it takes effect immediately, it spends real money, and it cannot be undone from here. **Call expenses_optimus_entity_fetch first, every time.** It is where `company_ids` comes from — this tool needs the `write_company_id` it answered, exactly one id — and where you see what the field is set to now and the absolute range a new value has to land in. Values from an earlier call go stale. **One field per call.** The allowance is at most 3 changes to one field of one ad group and 20 per person per hour, and only changes that actually go through are counted — a refusal costs nothing but the round trip. All three fields live here on TikTok, and the bid is the conversion bid price. One warning about budgets: an ad group switched to an unlimited budget in the ad manager is NOT detected here — Magnus stops updating the stored number and keeps the last one it saw, so a budget change may be accepted against a value that is no longer real. Read the budget in the ad manager before changing it on an ad group you did not set up yourself. **Units** — money is a decimal number in the ad account's own currency, in its main unit: 50 means $50.00. `status` is active or paused, this server's own two words; TikTok's own wording (ENABLE, DISABLE) is never accepted here. A change has to land between half and double the current value, and there is no override. **Say what you are about to do before you do it.** One ad account is routinely shared by several companies, so the change is visible in the reports of every company that shares it, not only the one you passed. **Ceilings and daily limits** — going up, a budget or bid may not be set above this server's ceiling: 5000 USD a day for a campaign budget, 1000 USD for an ad set or ad group budget, 200 USD for a bid, converted into the ad account's currency; the card's `bounds.max` already includes it. Decreasing and pausing are always allowed. Each person may also set at most 25000 USD a calendar day (UTC) in motion through this server — budget increases, the budgets of objects switched on and created budgets, summed — and the answer's `limits.per_day` says what is left and when it resets.
expenses_optimus_tiktok_adgroup_update
Changes the status or the daily budget of one TikTok campaign, in the ad account itself. This is a real change to live advertising: it takes effect immediately, it spends real money, and it cannot be undone from here. **Call expenses_optimus_entity_fetch first, every time.** It is where `company_ids` comes from — this tool needs the `write_company_id` it answered, exactly one id — and where you see what the field is set to now and the absolute range a new value has to land in. Values from an earlier call go stale. **One field per call.** The allowance is at most 3 changes to one field of one campaign and 20 per person per hour, and only changes that actually go through are counted — a refusal costs nothing but the round trip. There is no bid at campaign level on TikTok: bidding lives on the ad group. A campaign on an unlimited budget reports no budget at all, and that is refused with the reason rather than attempted — there is nothing to halve or double. **Units** — money is a decimal number in the ad account's own currency, in its main unit: 50 means $50.00. `status` is active or paused, this server's own two words; TikTok's own wording (ENABLE, DISABLE) is never accepted here. A change has to land between half and double the current value, and there is no override. **Say what you are about to do before you do it.** One ad account is routinely shared by several companies, so the change is visible in the reports of every company that shares it, not only the one you passed. **Ceilings and daily limits** — going up, a budget or bid may not be set above this server's ceiling: 5000 USD a day for a campaign budget, 1000 USD for an ad set or ad group budget, 200 USD for a bid, converted into the ad account's currency; the card's `bounds.max` already includes it. Decreasing and pausing are always allowed. Each person may also set at most 25000 USD a calendar day (UTC) in motion through this server — budget increases, the budgets of objects switched on and created budgets, summed — and the answer's `limits.per_day` says what is left and when it resets.
expenses_optimus_tiktok_campaign_update
The run log of one automation rule of "Company → Notifications", newest run first: what the rule saw, what it did and what it sent, one row per run. It answers "why did it not fire" and "what did it change". Read a run in this order. has_subject_result false — the condition did not hold on any row (for an info rule: no rows at all), so nothing further happened. metrics[] shows every metric that was judged, per breakdown row: is_passed, the threshold (value — null on an info rule, which has none), the measured number (result_value — on an info rule's expenses metric a string that also carries its share in total) and the earlier average it was compared with (compared_value — null except on trigger_rate rules); group_by is the row's breakdown keys and values, an empty object on a rule without group_by. has_handlers_result true — the handler produced a result row: an update, or a write it attempted and the network refused; on creative_tags, at least one creative was among the rows, whether or not a tag was placed. What actually changed is the expenses_entities[] rows of type update — value_old / value_new say what — and creative_tags[], the tags placed per creative. has_handlers_result false with has_subject_result true — the condition held and the handler acted on nothing; it is always false on a marketing or retention_chart rule, which has no handler. Either way expenses_entities[] says what happened to each entity, as type/sub_type: skip/data_unhealthy (a freshness check on installs, subscription events or expenses failed — nothing is written until it passes), skip/account_token_expired (the creator's connection to that account has expired), skip/frequency (this rule changed the entity less than frequency_hours ago), skip/frequency_external_service (someone or something else changed it in the network less than frequency_hours ago — a manual expenses_optimus_*_update counts), skip/paused_status (the entity or its parent is paused), skip/null_value (the entity has no value in that field — a campaign whose budget sits on its ad sets), skip/value_beyond_bound (the bounded, rounded target would not move the value in the action's direction), error/external_service (the network refused the write — value_new holds its answer — or there is no connection, "Linked account not found", or the parent entity could not be read), error/same_value (the write went through and the value did not change); update is the row that moved something. has_method_result true — the rule has a Slack method and the run reached the send step; delivery itself is not checked, so a channel the bot has left still reads true. It is always false on a rule without Slack. At most 100 runs per call, and meta.pagination.next_offset is the only way to read further back; meta.matched_rows is how many runs of the rule have a log row. A run's row reaches the log a few minutes after last_triggered_at moves in the list, so an empty log right after a run is not a missing run. A run whose row was never written is not listed at all — common on creative_tags rules, whose tag-placing runs often leave no row: for tagging, the tags on the creatives are the source of truth, not this log. This tool reads one company per call. When NOT to use: to see what a rule is set to, read companies_company_notification_fetch; a rule that has never run has an empty log and last_triggered_at null in the list.
companies_company_notification_log
What this project measures, and the place to start any funnel or conversion question. It answers two different things depending on how you call it. With no filters it returns the catalogue of the project as a whole — its named funnels with their steps already resolved to event names, the stages with how many events each holds, the anchors every conversion rate rests on, the data distortions that make numbers lie, and the events renamed or removed in the last six months. Nothing in there is about one event, and no event rows come back. With any of stage, step_key, step_roles, role, category or name_prefix it returns the event rows themselves, one per event — that is where the reasons for a drop-off live. Each event carries step_role: enter = the user entered the step, exit = passed it, dropoff = the attempt ended there, aux = everything else. A step's conversion is the enter/exit pair. step_key names the step an event belongs to, and the events that are not part of any step — clicks, toggles, technical ones — have none, so filtering by step_key never returns those. A row arrives as that skeleton plus any warning attached to it; what an event means — its description, the parameters it is safe to group by, the parameter that says why a dropoff happened — is included automatically when the filters match at most 40 rows, and on larger answers only when named in "with": narrow first, meaning follows. Event names are project-specific and cannot be guessed.
evtruck_event_names
Answers questions about in-app behaviour: how often an event happened, to how many users, broken down over time and by event parameters, app version or user properties. This is the product-analytics counterpart of marketing_timeline_report — it counts events the SDK sent, not spend or revenue. Get app_project_id from apps_app_project_list and event names from evtruck_event_names. Note that asking for "with" returns device identifiers per row, so the answer stops being an aggregate. Every breakdown comes back ranked — by users descending unless sort says otherwise — and a long result arrives one page at a time: meta.matched_rows says how many rows matched, meta.pagination.next_offset says where the rest of them starts, and meta.completeness says outright whether this answer is the whole set — so a full-looking page is never all there is. Grouping by event_name is the one call that answers "where do users drop off": each row carries its meaning, its stage, the step it belongs to and the parameters you can break it down by next. That ranking is a hypothesis about the order, not evidence of it — each event is counted on its own and nothing stops a user from firing a later event without the earlier one; the actual step order is in the funnels returned by evtruck_event_names, and a conversion rate comes only from evtruck_funnel. With group_by_date the answer is a set of trends instead: whole series are picked by their total over the range and a page ends where a series ends, so it holds fewer events over all your dates rather than all events over some of them. Before calling a fall an anomaly, read the distortions and lifecycle from that same catalogue: a series that stops dead on a release date is a rename, not a collapse, and one that starts mid-period is a release, not growth.
evtruck_event_statistics
Per-device lookup, not an aggregate: the raw event stream of ONE device, newest first. Requires an identifier — idfa, idfv, idfm or uid — so it answers "what did this user actually do", typically when investigating a single complaint. For counts across users use evtruck_event_statistics instead.
evtruck_event_device
The answer an A/B experiment was run for: each variant's money and conversion side by side with the control group, and the 95% confidence interval that says whether the difference is real. This is the only tool on this server that measures significance — everywhere else a difference between two numbers is just a difference. Take experiment_id from mutator_experiment_list, and read mutator_experiment_fetch first: it carries what the experiment is testing, its variants with the control group marked, its goals with their ids, and running_at, which is the day the measurement can start from. HOW TO READ IT. The answer is one block per goal, and inside it one row per variant. Find the control group by is_control_group, never by position: only the event-goal blocks are ordered with it first, and on a metric goal the row order is not guaranteed. Compare each variant against that row and never against another variant. An interval is {lower, upper, intersect, bayesian_probability} when it could be computed and [] when it could not, usually a zero denominator. intersect false is the verdict you are after — this variant's interval does not overlap the control group's — but it is only evidence when BOTH rows carry a real interval: a control group whose own interval is [] also yields false, and so does the control's own row, which has nothing to compare against. Check the control row first, and treat false as significance only if its interval is there. bayesian_probability is the chance this variant beats the control as a PERCENTAGE, 0 to 100, not a fraction — 99.97 means almost certain. It is null on the control's own row and also wherever it could not be computed, which happens on counts above 30000, so null is never evidence of anything. Two goals carry no interval at all: the revenue goal is a roll-up, and an event goal always answers the same seven numbers — installs, count, unique_count, cvr, unique_cvr and the two interval keys — whatever metrics asks for. UNITS. Every *_cvr is already multiplied by 100, and so are the bounds of its interval, so 0.35 means 0.35% and not 35%. Money is USD. ads_arpu_confidence_95 is the one to distrust: it is computed only when arpu or arpu_confidence_95 is also in metrics, and asked for alone it comes back carrying nothing but a bayesian_probability; its bounds, when they do arrive, are 100 times the ads_arpu they bound, while sales_arpu, arpas and the arppu intervals are in the unit of their metric; and its bayesian_probability computes to 100 on every non-control row whatever the data says. WHAT COSTS WHAT. This is the slowest read on the server — seconds normally, tens of seconds on an experiment with a long history — so ask for the metrics you will actually quote rather than the whole list. Event goals are counted by a separate service and add several seconds of their own, so they are computed only when you name their goal_ids explicitly; omit goal_ids and you get the metric goals alone, with meta saying which event goals were left out. WHAT NARROWS WHAT. countries, sources, product_codes, group_by and group_by_date narrow and break down the metric goals only. An event goal is always counted over the whole variant, so in one answer the two kinds of block can describe different populations — meta.caveats says so when it happens. The segmentation filters (idfm, user_props_first, user_props_last, performed_events) are different: they narrow the experiment's population itself and apply to everything in the answer. A draft experiment has no statistics at all and answers an empty array — that is its status, not an absence of data. installs is always present in every metric row, whether asked for or not: it is the denominator the rates and the intervals are built on.
mutator_experiment_statistics
The dated changes of one Facebook campaign, ad set or ad and of everything beneath it — ad sets, ads, creatives — read from the states Magnus recorded, so that a move in spend or CPA can be set beside what was edited and when. It changes nothing. **When to use** — someone asks what changed in a campaign, when a budget was raised or an ad set paused, whether targeting moved, or why a metric moved on a given day: read this first, then marketing_timeline_report for the effect. Give it the id of the object you already have; the tree beneath is resolved from the Magnus mirror, so an ad added after the campaign started is included. **When NOT to use** — not for the settings as they are now (expenses_optimus_entity_fetch reads the object live), not for metrics (no spend or installs here), not for who made a change (not recorded), not for Google Ads or TikTok (only Facebook has snapshots; other networks are refused naming the network). **What is recorded** — Magnus stores a state when it creates an object and when Facebook reports a change of status, daily budget or bid. Those fields are dated exactly. An edit of any other field — targeting, name, bid strategy, goal, schedule, creative — is seen only at the next such moment and is dated as an interval between two snapshots: read `dating` before `date_from`. `include_effective_status` adds Facebook's own status flips (review, learning, a campaign pause cascading onto every ad); it is off by default because one pause writes one flip per ad. Nothing before 2025-12-10 exists, and ad-level rows before 2026-04-01 are not read. `coverage` says how many objects of the tree have a recorded state at all; a gap there is named in caveats. **Dating** — `exact`: the day of Facebook\'s own edit stamp, in the ad account\'s day, which is the day marketing_timeline_report uses; `observed`: the day Magnus saw the new state; `interval`: the edit happened between date_from and date_to inclusive, the day itself is unknown; `created`: the object was created that day and new_value holds its initial settings. Records are chronological by date_from. **Ids and units** — entity_id is the network\'s own id as a string; a JSON number loses digits. `adgroup` is the Facebook ad set, as everywhere on this server. Money is in the ad account's currency in major units: a $50.00 daily budget is 50. `status` is active or paused, otherwise Facebook's own word lowercased; `effective_status` is always Facebook's word lowercased. A `targeting` record carries only the changed top-level keys, on both sides, and lists them in `changed_keys`. A `budget_owner` record says the daily budget moved between the campaign and its ad sets.
expenses_optimus_facebook_change_history
One automation rule of "Company → Notifications", whole, in the words the create tools take: `data.arguments` is exactly the argument set of the rule's kind — the same keys, the same shapes — plus status. To change a rule, copy data.arguments, change what should differ, add notification_id (this rule's id) and send the whole of it to the kind's _update tool: companies_company_notification_<kind>_update, where kind is data.kind. To copy a rule, send data.arguments without status to the kind's _create tool. Beside the arguments: the app the rule reads (app_id is the argument, app.name is for the person), the Slack channels with their names, who created it and when it last ran. A rule can carry what no create tool can send — filters nobody uses any more, a forecast horizon, a metric outside the catalog, a status handler that would activate rather than pause. Those sit under `data.not_authorable`, named, and meta.caveats says what an update does with them: the filters and the horizon are kept as they are, an activating status becomes a pause, and a metric outside the catalog has to be replaced before the rule can be updated at all. When NOT to use: to find a rule by name or kind, list them with companies_company_notification_list; to see what a rule did, read companies_company_notification_log.
companies_company_notification_fetch
One experiment, whole: every column of its row plus its relations, so it answers anything mutator_experiment_list left out. Several of those exist nowhere else — the free-text description of what is actually being tested, the timestamp of each status change (which is how you bound the period to measure it over), its variants with the control group marked, the goals it is measured by, and the segments and events that gate who enters it. This is where variant_id comes from; the list is a projection of six fields, so the pair is "list to find the experiment, fetch to read it". Reads mutator data, so it needs mutator rights rather than the evtruck ones.
mutator_experiment_fetch
One draft of a remote config, whole: the row mutator_remote_config_draft_list answers, plus operations — what was sent to mutator_remote_config_draft_create, exactly as it was sent, so a draft can be reworked into a new one: copy the operations, change what should differ, and send them with the current base_version — plus changes (what the draft adds, changes and removes against the version it was built on, by key and name) and warnings (what a reviewer should look at: a value changing its JSON type, a removed condition with the overrides lost with it, a condition that matches every device), both computed now against that version. The config itself is not here — mutator_remote_config_fetch reads the current version, and a draft's own values are in its operations. is_stale true means the config moved after the draft was built: it cannot be applied, and the person asks for a new one. A draft made in the UI has operations null. This tool reads one company per call.
mutator_remote_config_draft_fetch
The current remote config of one app project — Magnus → Mutator → Remote config, the version devices receive — read in two steps so that a large config fits an answer. Without parameter_keys it answers the STRUCTURE: version (the base_version a draft is built on), description, published_at and creator of the current version; totals; groups [{key, description, parameters}]; conditions in priority order (position 1 wins when a device matches several), each with its segments, performed_events and idfm — the vocabulary mutator_remote_config_draft_create takes back; and parameters [{key, description, group, value_type, value_bytes, overrides: [{condition, value_type, value_bytes}]}] — no values, only the JSON type and the weight in bytes of each. With parameter_keys it answers those parameters IN FULL — value and every override's value, whatever their size — and nothing else. Read the structure first, then only the values the task needs: totals.value_bytes is the weight of every value in the config, value_bytes per parameter is what one read costs, so on a large project ask in batches of keys rather than for everything. Keys are exact and case-sensitive; a key the config does not have is named in meta.caveats and the answer is partial. include_schema: true adds the project's JSON Schema whole under json_schema — it can weigh hundreds of KB and the server validates nothing against it; leave it out unless the shape of a value is the question. There are no ids anywhere: groups, conditions and parameters are addressed by key and name in every remote-config tool. A project without a config answers version 0 and empty collections. This tool reads one company per call; app_project_id is from apps_app_project_list, NOT the app_id. When NOT to use: the history of versions is not here, and neither are drafts — mutator_remote_config_draft_list has those.
mutator_remote_config_fetch
Answers "where do users drop off": how many users passed each step of an ordered sequence of events, and how many were lost between the steps. Get app_project_id from apps_app_project_list and the sequence itself from evtruck_event_names called with no filters — it returns the project's funnels with each step already resolved to the event names to pass here, in order. Two things from that answer must not go into this list: the steps under "branches", which only part of the users reach by design and which therefore deflate every step after them, and more than one route of the same "alternatives" group — use its union_event, or measure the routes separately. This tool imposes whatever order it is given and always returns a decline, so a wrong order is indistinguishable from a real drop-off — when a funnel says its order is not fixed, say which order you assumed. Note that with_lost_idfm and with_passed_idfm return lists of device identifiers, which turns the answer into per-user data. This tool reports no sample size of its own: when you split it by a user property or run it once per experiment variant, read the count on the first step of each group before calling a difference between them an improvement — on a few hundred users a gap of one or two points is noise, and nothing here will say so.
evtruck_funnel
Per-device lookup, not an aggregate: the install records the attribution providers have for ONE device — AppsFlyer and Adjust for mobile, the web tracker for web projects. Requires an identifier: idfa, idfv, idfm, uid or ip. Use it to find out where one user came from; for install counts use marketing_timeline_report.
evtruck_install_device
Lists the Facebook ad accounts you can create campaigns in — the account_id every expenses_optimus_facebook_*_create tool starts from, with the currency it bills in, the smallest daily budget it accepts and the largest this server will set. **Call this first, every time.** Do not carry an account id over from a report or from memory: this answer is what says whether the account is reachable under your own connection at all, and a create call against an account that is not on this list fails after the request has already been built. It reads one company per call — the one in `company_ids`, which is the company where you hold a managed Facebook connection; companies_company_list says which those are under managed_connections. **Units.** `min_daily_budget` is a decimal amount in the account's own `currency`, the same units every budget argument on this server takes — 1.00 means one dollar in a USD account. Meta's own MCP server and the raw Marketing API state this field in integer minor units (cents); never carry a number between the two. Read `currency` before quoting any amount to a person and never assume USD. **Ceilings.** `ceilings` states, in the account's currency, the most this server will set: `campaign_budget` and `adgroup_budget` per day, and `bid`. They are USD ceilings converted at today's rate; `converted` false means no rate could be read and the USD number is applied as is, which is stricter for every currency weaker than the dollar. A create or an increase above them is refused with no override — plan the budget arrangement within them or leave the larger amount to the ad manager. **Ids.** `account_id` is a bare numeric string with no `act_` prefix, which is the form every create tool takes. Ids are strings everywhere on this server because Facebook's are longer than a float can hold exactly. Response Guidelines: an account appears here only if your managed Facebook connection in the named company can see it, so a missing account means the connection does not reach it, the account is inactive, or it is reached from another of your companies — say that rather than that the account does not exist. `has_funding_source` false means the account cannot spend even once something is turned on. The list is not paginated and is not truncated: it is every active account that connection reaches. When NOT to use: this does not report spend or performance — marketing_timeline_report does that. It also does not list campaigns; expenses_optimus_entity_fetch reads one object.
expenses_optimus_facebook_ad_account_list
Lists the Facebook pages you can advertise from — the `page_id` every creative is published under — and, given a page_id, the Instagram account attached to that page. **Call this before creating a creative, every time.** A creative without a page is refused by the Marketing API with "Facebook Page is Missing", and a page you merely know the name of is not the same as one your connection holds a role on. This is what tells the two apart. **Two modes.** Without `page_id` you get the pages your connection in the named company has a role on, id and name. With one you get that page's card: its name, its `instagram_user_id`, and `leadgen_tos_accepted`, which says whether the page may run lead forms. **Instagram is required.** expenses_optimus_facebook_creative_create needs an `instagram_user_id`, so a page whose card answers null for it cannot be used for a creative yet — say so plainly and offer another page rather than creating without one, which is not possible here. Response Guidelines: a page missing from the list is one your connection has no role on, which is not the same as a page that does not exist — do not tell a person their page is gone. A card asked for by id refuses when the connection holds no role on that page, and that refusal is about your access rather than about the page. When NOT to use: this reports nothing about the page's audience, its posts or its performance. It exists to hand a page_id and an instagram_user_id to a creative.
expenses_optimus_facebook_page_list
Lists the Facebook pixels on one ad account, and — given a pixel_id — the conversion events that pixel has actually been seeing, in the exact words the ad set's promoted_object takes. **Call this before creating a sales ad set.** An OUTCOME_SALES campaign optimising for a website conversion needs a pixel_id in its promoted_object, and the Marketing API refuses the ad set without one, saying "Performance goal isn't available with this objective". Read the pixel here rather than guessing an id. **Two modes.** Without `pixel_id` you get the account's pixels, id and name. With one you get the events that pixel reported, each already shaped as a promoted_object — a `custom_event_type` on its own, or `OTHER` alongside the `custom_event_str` naming the event. Pass a whole entry through to expenses_optimus_facebook_adset_create unchanged. **Ids.** `account_id` is the bare numeric string from expenses_optimus_facebook_ad_account_list, with no `act_` prefix, and `company_ids` is the company_id that list answered beside it. Pixel ids are strings too. Response Guidelines: the event list covers a short recent window — roughly yesterday until now — so it is what the pixel has been seeing lately and not everything it has ever seen. An event a person expects and does not find may simply not have fired recently; say that rather than that the pixel cannot report it. An empty event list is not a reason to invent a custom_event_type. When NOT to use: this counts nothing. It does not say how many conversions there were or what they were worth — marketing_timeline_report answers that.
expenses_optimus_facebook_pixel_list
Lists the images and videos already uploaded for this advertiser — where the `image_hash` and `video_id` that expenses_optimus_facebook_creative_create takes come from. **Call this first, every time, and search it by name.** These files were uploaded from Google Drive and kept their real filenames, so a person asking for "the summer bundle creative" can be answered by passing that as `file_name`. Never invent a hash or a video id: Facebook does not check that an image hash exists when a creative is created, so a made-up one produces a creative that renders as nothing rather than an error. **Ids.** `image_hash` is a 32-character hex string and `video_id` is numeric — a creative takes one kind or the other, never both in the same call. `company_ids` is the one company whose files to read — the same id the creative will be created under. Response Guidelines: this reads Magnus's own uploader records, not Facebook's media library, and every file in it belongs to the company whose connection uploaded it. If the answer is empty for a company that plainly runs ads, the uploader has simply never been used there — say that, rather than that the account has no creatives. Files whose upload failed are left out because they carry no usable id. Rows are ordered newest first and paged: read meta.pagination.next_offset and pass it back as `offset` when there is more. When NOT to use: this is not a performance report and says nothing about how a creative did — marketing_timeline_report grouped on creative answers that.
expenses_optimus_facebook_media_file_list
Lists the Slack channels and direct messages YOUR Slack profile belongs to in this company — the ids every companies_company_notification_*_create tool takes as slack_channel_ids, with the name and the type of each (public_channel, private_channel, group, profile — a profile is a direct message to one person). **Call this first, every time** before creating or changing a rule that sends to Slack: an id that is not on this list is refused, and ids are Slack's own strings (C…, G…, D…), never guessable from a channel name. A channel the person names that is missing here is one their Slack profile is not a member of, or a public channel the Magnus Slack app has not been invited to — say so rather than that it does not exist. Needs the Magnus Slack app installed for the company and your own Slack profile connected in Magnus; without either the answer is a refusal that says which one is missing. Reads Slack live, one company per call. When NOT to use: this does not send messages and does not list the rules that post to a channel — companies_company_notification_list shows each rule's channels.
slack_channel_list
The notes people wrote on a project's timeline: one line each, saying what happened and on which date. It is the only place on this server where the cause of a spike or a drop is written down, so read it before explaining one — and report what it says as someone's note, never as a measurement. Keyed by app_project_id from apps_app_project_list, which is NOT the app_id of apps_app_list, and it reads one company per call. There is no date filter: the answer is every note of the projects asked for, so match the dates yourself against the period you are looking at.
apps_app_project_note_list
Lookup helper, not an answer in itself: the app projects (the SDK projects behind the product-analytics tools) available to the user, with their ids, platform and company_id. Every evtruck_* tool is keyed by app_project_id, which is NOT the app_id from apps_app_list — start here for those tools. Each row also carries the app_id of the app the project belongs to when it has one — many projects have none — and that is the way across: the evtruck_* tools count what users did, while the money it came to — spend, revenue, ROMI — is read by marketing_timeline_report under that app_id.
apps_app_project_list
Lookup helper, not an answer in itself: the apps available to the user — id, name, package_name, platform, company_id, the publisher it belongs to, and the date of its first install where there is one. Every report needs app_ids, so start here unless you already have them. Apps someone marked hidden are left out of this list, and every tool that takes app_ids refuses a hidden one anyway. If an app the user named is missing, say it is not among the visible apps rather than that it does not exist — apps_app_project_list is not filtered that way and may still show its project.
apps_app_list
Lookup helper, not an answer in itself. Lists the companies the current user belongs to, with their ids. Only needed to narrow another tool to specific companies via company_ids, or to answer "which companies do I have" — many tools cover all of them by default, and the ones that read a single company per call say so in their own company_ids argument. Each row describes your MEMBERSHIP, not the company: status is the membership (active, invited or not_active) and is_admin is your role in it. Only a company you are an active member of can be passed as company_ids, and this list is not filtered — an id it shows can still be refused there. **Where a write goes.** `managed_connections` says, per ad network, whether you hold a managed ad-account connection of your own in that company: `connected` lists the networks that work, `expired` the ones whose token has to be renewed in Magnus. The expenses_optimus_facebook_*_create tools and their four lookups read exactly one company and only one where Facebook is connected, so this field is where their company_ids comes from — take the id from here rather than trying companies one by one.
companies_company_list
Lists the automation rules of the UI section "Company → Notifications" — standing rules that run on a schedule, each on behalf of the person who created it: one short row per rule with its id, kind, name, status, schedule, window, app, the metrics as "metric condition value" strings, the handler in one line, the Slack channels it writes to, who made it and when it last ran. This is where notification_id comes from for every other companies_company_notification_* tool; companies_company_notification_fetch reads one rule whole. `kind` is the tab the rule sits on in the UI and decides which tools change it: marketing (a Slack alert on marketing metrics), ads_management (changes live budgets, bids or statuses in an ad account when the condition holds), creative_tags (tags creatives by performance), retention_chart (an alert on a saved evtruck retention chart). A paused rule is stored and inert; an enabled one runs at its schedule. Nothing is cut: the answer holds every rule of the covered companies that passes the filters, and the filters narrow rather than search — kinds, statuses and app_ids are exact sets. A rule that is not here does not exist in those companies, or your role cannot see them. When NOT to use: this does not say why a rule fired or stayed silent — that is companies_company_notification_log — and it does not read the messages Magnus itself sends about expired tokens; those are not exposed here.
companies_company_notification_list
Lookup helper for marketing_timeline_report: the creative tags of the user's companies, whose ids go into its creative_tags filter and whose rows name the buckets of group_by ["creative_tag_id"]. A tag labels what a creative is about — the theme or concept the user actually sees. It attaches to the creative NAME, so one tag covers every ad carrying that name across campaigns and networks. Most tags are thematic, set by the team or synced from the creative-production tracker; the low/middle/good/best family is not — those are quality labels assigned automatically by performance rules, a machine judgement of results, not content. Tags are company-wide, so no app or date is needed.
base_tag_list
Lookup helper for the product-analytics tools: the parameters one event carried over the given period. Their names go into the "where" conditions and into group_by_params of evtruck_event_statistics. Get the event name from evtruck_event_names first.
evtruck_event_params
Lookup helper for the mutator_experiments filter, and the other half of answering "how do we improve this": the A/B experiments of this app project — id, name, status, what activates them and how much traffic they cover, one flat row each. It is the way to find an experiment; mutator_experiment_fetch is the way to read one, and it is where variant_id comes from. Those six fields are a projection and not the whole row: fetch answers with every column an experiment has — the free-text description of what is being tested, the app project, the timestamp of each status change — plus the variants, goals, segments and gating events. A field absent from a row here is not missing from the data; it is in fetch. The projection is deliberate: the variants alone are most of the weight of a row, and all of it is wanted for one experiment at a time rather than for every one at once. Compare a variant against the control group to turn a hypothesis into a measured answer. Reads mutator data, so it needs mutator rights rather than the evtruck ones. It answers with every experiment of the project unless statuses narrows it, and nothing is trimmed on the server — on a project with a long history that is a long answer, and it is meant to be: a finished test is as worth reading as a live one. Narrow by statuses when the question is only about what is running now.
mutator_experiment_list
Lookup helper for marketing_timeline_report: the values one dimension can take (campaign names, sources, ad accounts, ...) for the given apps and date range, so its filters can be built from real ids. Some dimensions can themselves be narrowed by sources, campaign_ids or adgroup_ids. Skip it when the user already named the exact value — except for ad_accounts: a Facebook account is stored with the act_ prefix (act_1234567890) and people write it without one, so look it up here rather than trusting the number in the question. Two dimensions are different in kind and neither feeds a filter. url_params answers with a map of the parameters the ad links carry (utm_source, mode, ...) and the values each one takes; those NAMES go into marketing_timeline_report as group_by_url_params, which is the only way to split spend or revenue by such a parameter — campaign names are a different cut and not a substitute. And config_dates is not a filter value but the versions of the sales forecast that cover the given apps and period, which is what marketing_timeline_report accepts as config_date — pass it the same app_ids and dates you will report on, or the dates it returns may hold no data for the period you ask about. Ask for it only when the user wants an earlier forecast: the report reads the newest version by default, so an ordinary question needs neither this dimension nor config_date.
marketing_analytic_dimension_values
Lookup helper for finances_risk_report and finances_risk_approval_rates: the values one risk dimension can take (error codes, card brands, legal entities, payment providers, ...), to build filters for finances_risk_report and finances_risk_approval_rates. One company per call. payment_services is not here: its only values are solidgate and truegate, already spelled out in the reports' schema.
finances_risk_dimension_values
The drafts of one app project — proposed versions of its remote config that a person applies or rejects in Magnus → Mutator → Drafts — newest first, one row each: id, label (base_version.version, how the person finds it), status (draft — waiting for the person; applied — published as applied_version; rejected), source (mcp or ui), description, creator, created_at, current_version and is_stale (the config moved after the draft was built, so it cannot be applied — ask for a new one), and on a resolved draft applied_version, resolver and resolved_at. This is where draft_id comes from for mutator_remote_config_draft_fetch, which answers the operations a draft was built from and its changes and warnings; the rows here carry none of those. status narrows to one status; omitted, every draft of the project is listed, applied and rejected ones included — they are the history of what was proposed. At most 100 rows per call; meta.pagination.next_offset continues, meta.matched_rows is how many drafts matched. Nothing here changes anything, and nothing reaches devices from a draft until a person applies it. This tool reads one company per call. When NOT to use: the config itself is mutator_remote_config_fetch; to propose a change, mutator_remote_config_draft_create.
mutator_remote_config_draft_list
Lookup helper for companies_company_notification_retention_chart_create and _update: the saved retention charts of one app project — id, name, the date grain, and whether an alert can be put on the chart at all. usable_for_notification is false, with unusable_reason, for a chart that has more than one segment, more than one return event or groups by user properties: the alert reads one day-0 conversion series and cannot follow several. is_hourly says whether the chart is built by the hour, which is the only kind a one-day window (period_from_days_ago equal to period_to_days_ago) works on. Charts are keyed by app_project_id from apps_app_project_list — NOT the app_id of apps_app_list; one app can have several projects. Reads one company per call, and only charts of the project you may read. Nothing is trimmed: the answer holds every retention chart of the project. When NOT to use: this does not compute retention — evtruck_retention does — and it does not list funnel or event charts.
evtruck_retention_chart_list
Lookup helper, and the thing to call before answering "how do we improve this": the user properties this app project collects, with the values each one takes. Their "option" names are what goes into user_props_last / user_props_first and into group_by_user_props_last / group_by_user_props_first of the other product-analytics tools — without them a segment filter can only be guessed at, and a wrong name silently matches nothing. Use it to compare one segment against another: country, device type, app version, locale, traffic source. A few properties hold personal data or a means of access rather than a vocabulary — an email address, a push token — and those come back with values_hidden true instead of a sample: the name and values_count are there, so you can still filter on them (set / not_set is usually what such a property is worth), but the values themselves are not returned and asking again will not produce them.
base_segment_options
Answers questions about marketing performance: installs, spend, revenue, ROMI and the rest, for the given apps and date range, optionally broken down over time with group_by_date and by dimensions with group_by. Get app_ids from apps_app_list, filter values from marketing_analytic_dimension_values and the meaning of each metric from marketing_timeline_report_metrics. Five arguments carry a date or a horizon and they answer different questions: date_start/date_end is the reporting period (required, always take it from the question), forecast_n_days is a cohort age in days after install, forecast_date_start/forecast_date_end bound the calendar window of payments the forecast is summed over, and config_date is which version of the forecast is read. Everything except the reporting period has a default that fits almost every question — today for forecast_date_start and config_date, one year ahead for forecast_date_end, [60] for forecast_n_days — so pass one only when the user named a cohort age, a calendar window or an older forecast, and never fill them from the dates in the question. Whenever the server filled one of them in, the answer reports the values it actually used in meta.effective_arguments.forecast_window: quote those when you state a forecast number instead of assuming them. The report can rank, filter and window its own rows — sort ranks by any requested scalar metric, where_metrics keeps only rows passing numeric conditions, limit/offset return one stable page. data.total answers whole-scope questions (a share-of-total, the period's overall figure) and aggregates every matched row — it is ABSENT whenever where_metrics or limit is used; those two argument descriptions say how to get subset totals.
marketing_timeline_report
Lookup helper for marketing_timeline_report: explains the metrics it accepts — what each one measures, its unit, what it is computed from and how its total is built. Call it before choosing between look-alikes — revenue, cohort_revenue, n_day_revenue and predicted_revenue answer different questions, and picking the wrong one changes the answer without any error.
marketing_timeline_report_metrics
Pauses or resumes one Facebook ad, in the ad account itself. This is a real change to live advertising: it takes effect immediately and it cannot be undone from here. **Call expenses_optimus_entity_fetch first, every time.** It is where `company_ids` comes from — this tool needs the `write_company_id` it answered, exactly one id — and where you see what the ad is set to now. Values from an earlier call go stale. **One field per call.** The allowance is at most 3 changes to one field of one ad and 20 per person per hour, and only changes that actually go through are counted — a refusal costs nothing but the round trip. Status is the only thing writable on an ad anywhere: money lives one level up. If you meant to change a budget or a bid, the object you want is the ad set, and expenses_optimus_entity_fetch on this ad names it as `parent`. **Units** — `status` is active or paused, this server's own two words. The network's own wording (ACTIVE, ADSET_PAUSED, PENDING_REVIEW) is what the card reports separately as `current.status_vendor`, and it is never accepted here. **Say what you are about to do before you do it.** One ad account is routinely shared by several companies, so the change is visible in the reports of every company that shares it, not only the one you passed. **Ceilings and daily limits** — going up, a budget or bid may not be set above this server's ceiling: 5000 USD a day for a campaign budget, 1000 USD for an ad set or ad group budget, 200 USD for a bid, converted into the ad account's currency; the card's `bounds.max` already includes it. Decreasing and pausing are always allowed. Each person may also set at most 25000 USD a calendar day (UTC) in motion through this server — budget increases, the budgets of objects switched on and created budgets, summed — and the answer's `limits.per_day` says what is left and when it resets.
expenses_optimus_facebook_ad_update
Pauses or resumes one Google Ads ad, in the ad account itself. This is a real change to live advertising: it takes effect immediately and it cannot be undone from here. **Call expenses_optimus_entity_fetch first, every time.** It is where `company_ids` comes from — this tool needs the `write_company_id` it answered, exactly one id — and where you see what the ad is set to now. Values from an earlier call go stale. **One field per call.** The allowance is at most 3 changes to one field of one ad and 20 per person per hour, and only changes that actually go through are counted — a refusal costs nothing but the round trip. Status is the only writable field: money lives on the ad group and the campaign. Performance Max asset groups are addressed here too — Magnus records them at ad level, and their parent is the campaign itself rather than an ad group, which is what expenses_optimus_entity_fetch reports as `parent`. Read that field rather than assuming. **Units** — `status` is active or paused, this server's own two words. The network's own wording (ENABLED, PAUSED, REMOVED) is what the card reports separately as `current.status_vendor`, and it is never accepted here. An ad removed in Google Ads cannot be resumed from anywhere, including here. **Say what you are about to do before you do it.** One ad account is routinely shared by several companies, so the change is visible in the reports of every company that shares it, not only the one you passed. **Ceilings and daily limits** — going up, a budget or bid may not be set above this server's ceiling: 5000 USD a day for a campaign budget, 1000 USD for an ad set or ad group budget, 200 USD for a bid, converted into the ad account's currency; the card's `bounds.max` already includes it. Decreasing and pausing are always allowed. Each person may also set at most 25000 USD a calendar day (UTC) in motion through this server — budget increases, the budgets of objects switched on and created budgets, summed — and the answer's `limits.per_day` says what is left and when it resets.
expenses_optimus_google_ad_update
Pauses or resumes one TikTok ad, in the ad account itself. This is a real change to live advertising: it takes effect immediately and it cannot be undone from here. **Call expenses_optimus_entity_fetch first, every time.** It is where `company_ids` comes from — this tool needs the `write_company_id` it answered, exactly one id — and where you see what the ad is set to now. **One field per call.** The allowance is at most 3 changes to one field of one ad and 20 per person per hour, and only changes that actually go through are counted — a refusal costs nothing but the round trip. Status is the only writable field: money lives on the ad group. `status` is active or paused, this server's own two words; TikTok's own wording (ENABLE, DISABLE) is never accepted here. **Say what you are about to do before you do it.** One ad account is routinely shared by several companies, so the change is visible in the reports of every company that shares it, not only the one you passed. **Ceilings and daily limits** — going up, a budget or bid may not be set above this server's ceiling: 5000 USD a day for a campaign budget, 1000 USD for an ad set or ad group budget, 200 USD for a bid, converted into the ad account's currency; the card's `bounds.max` already includes it. Decreasing and pausing are always allowed. Each person may also set at most 25000 USD a calendar day (UTC) in motion through this server — budget increases, the budgets of objects switched on and created budgets, summed — and the answer's `limits.per_day` says what is left and when it resets.
expenses_optimus_tiktok_ad_update
Answers questions about how payment attempts end: approval rate over time and its per-bucket breakdowns, plus the same risk metrics as finances_risk_report. split_by_date is required and sets the buckets of the four split metrics — risk_split_approval_rates, risk_split_error_rates and their cohort twins — which are the ones carrying the series; the scalar rates sum every bucket into one number per row, so a series of them needs group_by_date. One company per call.
finances_risk_approval_rates
Lookup helper: explains the metrics finances_risk_approval_rates accepts — what each one measures, its unit, what it is computed from and how its total is built. Payment risk metrics come in period, cohort and per-bucket flavours that answer different questions — check here before choosing.
finances_risk_approval_rates_metrics
Answers questions about payment risk for the given apps and date range: transaction volume, refunds, fraud alerts, disputes, chargebacks, claims and the approval rate, broken down by provider, card brand, error code and more. One company per call.
finances_risk_report
Lookup helper: explains the metrics finances_risk_report accepts — what each one measures, its unit, what it is computed from and how its total is built. Payment risk metrics come in period and cohort flavours that answer different questions — check here before choosing.
finances_risk_report_metrics
Subscription revenue and the payment funnel per product_code (the store/PSP SKU), for the given apps and date range. It is an install-cohort report: for most metrics date_start and date_end select the users who INSTALLED in that window and then count every payment those users ever made, rather than the payments made inside it — the metrics argument says which ones work the other way. Every row is one SKU inside one app: there is no group_by argument, the breakdown is fixed at product_code plus app_id, so a SKU sold in several of the requested apps comes back as one row per app and each row carries its app_id — add those rows up yourself before quoting a number for the SKU. Rows also carry a product_id the report adds on its own; quote product_code as the identifier, not that. Get app_ids from apps_app_list, filter values from marketing_analytic_dimension_values, and the meaning of each metric from predictions_product_revenue_metrics — the metrics argument says what the tricky ones mean and which dates the period selects for them. To discover which SKUs exist, call without product_codes and ask for at least one measured metric such as sales_revenue: rows are built from the metrics you request, so the product_* attributes alone answer no rows at all, and the SKUs you get are the ones the sources behind your metrics saw in the window. There is no separate lookup tool, and the product_codes list in finances_risk_dimension_values is a different population — do not take codes from it. This report has no spend, no ROMI, no CPI and no ad revenue, and that is a property of the data rather than a gap: spend is attributed to campaigns, and ad revenue cannot be split by SKU at all. Do not rebuild them by dividing this report's revenue by spend from marketing_timeline_report — that is one SKU's revenue over the whole app's cost. Use marketing_timeline_report for these same numbers without the SKU split, or split by country, campaign, creative or ad; it has no product dimension, so no single call anywhere crosses a SKU with another dimension — cross them with this tool's filters instead. finances_risk_report also breaks down by product_code: its numbers and the risk_* metrics here read the same payment-gateway data and should agree, while the rest of this report reads subscription events attributed to an install, net of the store cut. Use that tool for provider, card-brand and error-code questions. The answer carries no total row: an honest total over a per-SKU breakdown would need a second full computation. Sum additive metrics over the rows yourself and recompute ratios from their components — but do NOT sum paying_users or the funnel counts, which count distinct users per SKU, and active_subscriptions, which is a stock. data.last_update is when this answer was computed, never a data-freshness signal.
predictions_product_revenue_report
Lookup helper for predictions_product_revenue_report: explains the metrics it accepts — what each one measures, its unit, what it is computed from and how it aggregates. Call it before choosing between look-alikes: sales_revenue and cohort_sales_revenue differ by which date the reporting period selects, the product_* attributes describe the SKU instead of measuring it, and picking the wrong one changes the answer without any error.
predictions_product_revenue_metrics
Proposes a new version of one app project's remote config as a DRAFT. Nothing reaches devices from this tool: a person compares the draft with the current version in Magnus → Mutator → Drafts and applies or rejects it, and only applying publishes. This is the only way a remote config changes over MCP. **Read first.** mutator_remote_config_fetch without parameter_keys for the structure, then with parameter_keys for the values you are going to change — a value is sent whole, so changing part of one needs the current one. base_version is the version of that structure read; if the config moved in between, the draft is refused with both numbers — read again, never guess. **Send only what changes, as operations:** set_groups / remove_groups, set_conditions / remove_conditions / condition_order, set_parameters / remove_parameters, set_overrides / remove_overrides. Everything is addressed by group key, condition name and parameter key — there are no ids. What you do not mention stays exactly as it is; a deletion is only ever a remove_*, never an omission. A set_* for a name that does not exist creates it; to rename, send rename_to and every reference follows. A field you leave out is unchanged; a list you give (segments, performed_events) replaces the list whole; a value is sent whole; "idfm": null removes the idfm; "group": null ungroups a parameter; a new parameter needs a value. A new condition lands last — the lowest priority, so it cannot shadow an existing one — unless condition_order names every condition in the order that wins first. Refused, with the names that exist: an unknown name in a remove_*, in set_overrides, remove_overrides or condition_order, or as a group; a duplicate within one list; a rename onto a taken name; a condition_order that is not exactly the resulting set. The whole config that results is validated with the rules a publish from the UI passes, and a refusal names the element. **A condition** is segments (option from base_segment_options — a name that does not exist matches nothing and reports no error), performed_events (name from evtruck_event_names) and idfm, the same vocabulary as the evtruck filters. A condition with none of the three matches every device, and the answer warns about it. **description** is what the reviewer and the version history will read: what changed and why, in one sentence, at most 255 characters. The answer is the draft: label (base_version.version, e.g. 5756.1 — how the person finds it), changes (what the draft adds, changes and removes, by key and name) and warnings — repeat the warnings to the person verbatim: a value changing its JSON type, a removed condition with the overrides lost with it, a condition without criteria. review_url is the link to the draft's compare screen in Magnus — hand it to the person. **Say what you are about to propose before you do it** — the description and the changes — and let the person stop you. Every refusal here happened before anything was written: nothing was created; fix what it names and call again. Drafts do not expire and do not block each other; a draft whose base version is no longer current cannot be applied, and the person asks for a new one. One company per call; app_project_id from apps_app_project_list. Example — one group, one condition and one parameter on version 5756: {"app_project_id": 599, "base_version": 5756, "description": "US extended to Canada; specialOffer: price 9.99, moved to group paywall", "set_groups": [{"key": "paywall", "description": "Paywall and special offers"}], "set_conditions": [{"name": "US", "segments": [{"option": "geo_country", "type": "string", "condition": "=", "value": ["US", "CA"]}]}], "set_parameters": [{"key": "specialOffer", "group": "paywall", "value": {"enabled": true, "price": 9.99, "trial_days": 3}}]}
mutator_remote_config_draft_create
Start here for any question about one Facebook, Google Ads or TikTok campaign, ad group or ad: give it the id the ad network itself uses and it works out which network and which level that id belongs to, re-reads the object from the network, and describes it. It changes nothing in the ad account. **When to use** — someone names an object id and asks what it is set to, why it is not delivering, or whether something about it can be changed. `editable` is the set of fields this server can write, `not_editable` says why each of the others cannot, `bounds` gives the allowed range as absolute numbers — half to double the current value, capped going up by this server's ceiling for the field — and `limits` says what is left: changes this hour per field and per person, and for the calendar day (UTC) how many objects may still be created and how much money, in USD, may still be set in motion. For a Facebook campaign it also says whether the budget is held by the campaign or by its ad sets, which is what decides whether an ad set under it may carry one; for a Facebook ad set and ad it returns the settings themselves — targeting, goal, the creative — in the words the create tools accept. **When NOT to use** — not to find an id: spend, installs and revenue per campaign come from marketing_timeline_report, and this tool answers about one object whose id you already have. Not to read performance: it returns settings and no metric of any kind. Not for the other networks Magnus collects (Apple Search Ads, Snapchat, Unity Ads, ironSource, AppLovin, Mintegral, Pinterest) — their campaigns appear in the same reports, but here they answer with a refusal, because Magnus has no write integration for them. **Ids** — `entity_id` is the network's own id, passed as a string. Do not send it as a number: Facebook ids run past what a JSON number holds exactly and the low digits are silently lost. The same id can exist twice when one ad account is linked by two companies; then the answer is a refusal listing each candidate with an `entity_ref`, and you call again with the same `entity_id` plus the `entity_ref` of the one you meant. `entity_ref` is opaque: copy it verbatim, never build one. **Units** — `budget` and `bid` are decimal numbers in the ad account's own currency, in its main unit: a $50.00 daily budget is 50, not 5000. `ad_account.currency` says which currency, and two accounts on two networks can both read 120 and mean different money. `status` is active or paused, this server's own two words; the network's own wording (ENABLE, ADSET_PAUSED, PENDING_REVIEW) is reported separately as `current.status_vendor` and a campaign set to active can still read PENDING_REVIEW there. **What comes back depends on `entity_type`, so read that first.** A **campaign** carries `current` with its objective, budget and bid strategy, `budget_owner` saying whether the budget is held here or by its ad sets, a live top-level `bid_strategy` in the exact word expenses_optimus_facebook_campaign_create takes — `current.bid_strategy` is a display label and is never sendable — and `adgroup_ids`. An **adgroup** carries `current`, `ad_ids`, and its settings in the words expenses_optimus_facebook_adset_create takes: `targeting`, `optimization_goal`, `destination_type`, `promoted_object`, `attribution_spec`, `bid_strategy`. An **ad** carries `current`, its `creative_id`, and `creative` — the whole creative flat, under the same keys expenses_optimus_facebook_creative_create accepts. Everything under `creative` is authored by creative_create; the ad itself — its name, its ad set, the creative_id hand-off — by ad_create. The authoring fields are Facebook only, and they are a projection, not the raw object: settings the create vocabulary has no words for — a saved audience, an app promoted object — are listed as paths under `authoring_unsupported` (`creative_unsupported` for a creative), meaning a re-create authored from this card would lose exactly those. **Copying goes through this card.** Read the object here, change what the person asked for, and send the fields to the matching create tool — the authoring blocks are stated in its exact vocabulary so they can travel verbatim. Two honesty rules: whatever `authoring_unsupported` or `creative_unsupported` names will NOT carry over, say so before creating; and an ad is duplicated by reusing its `creative_id`, never by re-authoring identical content, which Facebook would deduplicate back to the same creative anyway. **Response Guidelines** — 1. Of `current`, only `status`, `status_vendor`, `budget` and `bid` are what the network answered during this call, and `synced_at` is when; `current.bid_strategy`, `date_start` and `objective` are not re-read by it — no sync path writes them — so they are last-collection values and must not be quoted as current. The top-level `bid_strategy` on a Facebook campaign or ad group is different: read live, in the create vocabulary, and it is the one to author a copy from. 2. This tool answers only from the ad network. If you have no managed connection in a company where you may manage expenses, it refuses rather than answering from stored data; a partial answer means the card was read but something named in `caveats` was not. 3. `bounds` are absolute numbers, not multipliers, and a value outside them is refused with no override — the top end is the smaller of double the current value and the server's ceiling, and a value already above the ceiling can only go down. 4. A field missing from `editable` is missing for the reason `not_editable` names — do not look for another tool. 5. `write_tool` names the tool that can change the object; when it is absent, this server has no write tool for that network and level and the answer says so in caveats. 6. `adgroup_ids` and `ad_ids` come from the Magnus mirror, so a child created moments ago may not be listed yet — say that rather than reporting it as absent. 7. When `authoring_unsupported` or `creative_unsupported` is present, the object carries settings the create tools cannot express: a re-create built from this card will not carry them, so name them before proposing one rather than after it went out.
expenses_optimus_entity_fetch
Answers "do users come back": of the users who performed the starting event in each period, how many performed a returning event 1, 2, 3 … periods later. The answer is a cohort grid, so group_by_date is required. Get app_project_id from apps_app_project_list and event names from evtruck_event_names. The grid is filled up to the bucket that contains today, so the last offset of a recent cohort covers a period that has not finished: read a fall there as an incomplete bucket, not as churn.
evtruck_retention
How do I improve a ChatGPT Plugin's discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are Magnus alternatives on ChatGPT?
As of 2026-09-13, Magnus competes with Amplitude, Amplitude EU, Clics, Customer Journey Analytics, Datadog Experiments, KrystalView, Mixpanel, Pendo, PostHog, Savri, SEO Programático, Statsig, Subtext, Userflow, Wingify in ChatGPT Product Analytics & Experimentation, ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.