Connect an assistant

Point Claude, or any assistant that speaks MCP, at your own figures. It reads the same P&L you do, and it can propose a change to your cost model that you approve in the conversation.

Connect it

In Claude: Settings, Connectors, Add custom connector, and paste this address.

https://vecksapp.com/api/mcp

Claude reads the address, works out that it needs to sign you in, and shows you two settings it has already detected. Leave both as they are and press Connect. Either OAuth client option works: Vecks supports both the hosted client metadata Claude recommends and registering a client of its own.

You land on a Vecks page. Sign in if you are not already, choose which company the assistant should reach, decide whether it may change costs as well as read them, and approve. That is the whole setup. The connection then appears under Configure, Assistants, where you can end it at any time.

Two things worth knowing before you start. Only an owner can connect an assistant, because what it reaches includes supplier rates and individual orders. And one connection reaches one company: if you run several, add one connection each, which is also what keeps it unambiguous which figures an answer is about.

What it is allowed to do

A connection is either read only or read and change, and you choose which when you approve it. A read-only connection is not even shown the tools that change things, so it cannot spend a turn trying.

Reading reaches everything you can read in Vecks: the statement, the breakdowns, your cost rules, your products, your settings, and individual orders. Opening an order is written to the access log, exactly as it is when you open one yourself, so there is a record of what was read and by whom.

Changing is bounded, and the bound is structural rather than a promise. A change is always proposed first: it is run against your real orders inside a transaction that is rolled back, and what comes back is the whole P&L before and after it. Committing takes the id of that proposal and nothing else, so there is no way to commit a change whose effect was not produced first. A proposal can be used once and expires after fifteen minutes, and every commit is recorded permanently, outliving the connection that made it.

The reason for that shape, said plainly: Vecks reads text it does not control, such as invoice lines, spreadsheet cells and campaign names. A model holding a write tool is a model that can be argued with. Preview-then-commit is what keeps an argument from becoming a silent change to your numbers.

Reading tools

14 tools, available to every connection. The indented text under each is what the assistant itself is told, written for the model choosing between them.

None of them changes anything, and each says so to your assistant, so most assistants let you allow reading once instead of asking before every question. If yours asks each time, that is its setting rather than ours: look for an always-allow option on the approval it shows you.

ads_report“Which ads are wearing out, and which are worth more budget?”

Every Meta ad that spent in the period with its spend in your currency, Meta's purchases and value on their attribution window, cost per Meta purchase, hook and hold or link CTR, its launch date, its verdict against its own past, whether it is readable yet, and the three equal periods before it; with the account median, the campaigns and the market strip beside them.

An ad's spend is its share of what the P&L booked for its campaign and day, so the ads add up to the statement. There is no profit per ad: no order names its ad, and the answer says so rather than allocating one.

InputTypeMeaning
campaignstring, optionalonly the ads of one campaign, by its id or a part of its name
periodstring, optional'7d', '14d' or '30d' (the last N closed days), a month 'YYYY-MM' (a running month is its closed days), or 'YYYY-MM-DD..YYYY-MM-DD'; defaults to '7d'
verdict"fatiguing" | "limited" | "holding" | "not readable yet" | "never delivered", optionalonly the ads with this verdict; the counts, median and strip stay whole

Results per Meta ad for a period, read beside the P&L and never booked: per ad its spend in the reporting currency (its share of what the P&L booked for its campaign-day, so the ads of a campaign sum to the statement), Meta's purchase count and value on the ad set's attribution window, cost per Meta purchase, hook and hold rate for a video or link CTR for a static, launch date and days live, the verdict (fatiguing, limited, holding, not readable yet, never delivered) with the multiple over the ad's own past and the past it was read against, whether it is readable and the spend still needed, and the three equal periods before; plus the counts behind the page's sentence, the account median with its n, the campaigns with their booked spend, and the market strip (contribution before and after ads per market, with the per-order figure). Every figure says whether Meta reported it or Vecks derived it. It refuses a profit, margin or return per ad, because no order names its ad and a per-ad margin would be an allocation nobody decided; the answer carries the refusals and their reasons, so say them rather than computing one.

get_allocation_gap“Which orders are behind the shipping cost that no product carries?”

The orders, campaigns or monthly amounts of a cost that a market, product or cost center does not reach in a month, each with what it booked and why, biggest first.

The same rows the cost model's allocation footer counts as a shortfall, so the two never disagree.

InputTypeMeaning
dimension"markets" | "products" | "costCenters"
item_keystringthe P&L line's key (from get_layout)
monthstringYYYY-MM

The rows of a cost that a dimension does not reach in a month: the orders (with ids), campaigns or monthly amounts behind the cost model's "X of Y allocated to products / markets / cost centers" shortfall, each with what it booked and why the money cannot reach a product, market or cost center (a charge per order not split by product, an order with no product to split by, a campaign with no market, a monthly amount). Use it to name the exact orders behind a gap instead of guessing.

get_breakdown“Which market is actually profitable? How much came from returning customers?”

The same statement split by one dimension: market, product, sales channel, campaign, cost center, or customer (new, returning, or unknown where the channel supplies no customer, as Amazon never does), with every stage per value.

Costs that carry no value for the dimension you chose are reported as unallocated rather than spread, so a market figure is never flattered by a cost that was never attributed to it.

InputTypeMeaning
dimension"market" | "sku" | "channel" | "campaign" | "cost_center" | "customer_type"
fromstringISO date YYYY-MM-DD
tostringISO date YYYY-MM-DD

P&L broken down by one dimension (market, sku, channel, campaign, cost_center, customer_type) for a date range. Amounts per dimension value and stage. customer_type is new, returning or unknown, from the order's rank in its customer's history; Amazon orders are always unknown.

get_coverage“Which products and markets have no cost rules?”

The per-unit cost of every product in every market, broken into its components, and the per-order rules beside it.

The cost coverage matrix: per-unit cost per SKU and market with per-component breakdown, plus the per-order rules. Shows exactly which SKU/market combinations have missing or partial cost rules.

get_inventory“How many Hammer Downs do we have left, and how long will they last?”

Stock per product: on hand as the last count plus what arrived minus what sold since, units sold per day over the last 30 days, days left with the run-out date, and the shipments on their way.

On hand is derived from a count and the ledger, never kept as a running figure. A product nobody has counted reads as not counted rather than zero; a refund covering a whole order is read as the goods coming back.

Stock per product: on hand (last count + received - sold + returned since it; null = never counted), the last count (with its id), units sold and returned in the pace window, the selling pace in units per day, days left and the run-out date, open shipments (incoming units, next expected date, ids). Counts and shipments are recorded through propose_change (stock_count, inbound_create, inbound_receive, inbound_delete); none of them touches the ledger.

get_layout“What does my statement look like?”

The structure you have arranged: your subtotals in order, the sections under each, and the lines in each section, with the ids a change refers to.

Every one of these is yours to rename, move or remove. The statement is not a fixed shape we impose.

The statement's structure as the merchant has arranged it: subtotals in order (with ids), the sections under each (ids, labels), and the cost lines in each (item keys, labels). Everything here is the merchant's own and can be renamed, moved, reordered, added or deleted through propose_change (section_update, subtotal_update, cost_move, section_assign, item_create ...). Read it before saying something cannot be changed.

get_order“Why did this order cost that much?”

One order as the ledger booked it: every revenue and cost row, the rule that produced each, quantities, the exchange rate, and anything flagged. An order number is only unique within its channel, so give the channel when you know it.

Opening an order is written to the access log, exactly as it is when you open one in Vecks.

InputTypeMeaning
channel"woocommerce" | "shopify" | "amazon", optionalthe channel the order was placed on; without it, an order number shared by two channels reads as one order
order_idstring

One order exactly as booked in the ledger: every revenue and cost row with the matched rate (scope, note), quantities, FX, and any data-quality flags. Use to answer 'why does this order cost X'. An order number is only unique within its channel, so give the channel whenever it is known.

get_pnl“What did we make last month?”

Every line of your statement for the dates you give, in your reporting currency, with the order count. The same figures the dashboard shows, from the same ledger.

Refund timing is a choice: 'actions' puts a refund on the day the order was placed, 'reconciliation' on the day the money went back.

InputTypeMeaning
fromstringISO date YYYY-MM-DD
timing"actions" | "reconciliation", optional
tostringISO date YYYY-MM-DD

P&L summary for a date range: every line (revenue, costs, profit stages) in EUR, plus the order count. timing 'actions' books refunds on the order date, 'reconciliation' on the refund date.

get_settings“Which countries count as which market?”

Your start date and reporting currency, the market map, and the rules that decide what each ad campaign counts for.

Tenant settings and mappings: start date, reporting currency, the market map (which ship countries are broken out as their own market; everything absent books as 'Others'), and the campaign rules (conditions on a campaign's platform, name, delivery country or tag → the markets it counts for, with a spread, a product and a cost center).

get_setup_health“Is this month's profit figure trustworthy yet?”

How completely the month's orders carry their costs, per category, plus the gaps that are blocking and the data-quality issues by kind.

Four states: loaded, awaiting actuals, a rule exists but nothing matched, and no cost logic at all. The last two are why a profit figure can be too high.

InputTypeMeaning
periodstring, optionalYYYY-MM or YYYY; defaults to the current month

How completely costs are attributed for a period (YYYY-MM or YYYY): per-category states (green = loaded, amber = estimates awaiting actuals, red = a rule exists but something didn't match, grey = no cost logic at all), the blocking gaps, and data-quality issue counts by kind.

list_fixed_costs“What are our monthly overheads?”

Every monthly amount with its P&L line, value, currency and the months it runs.

A monthly amount is spread daily, so a month is never a step at the end of it.

Every fixed (monthly) cost line with id, P&L line, label, monthly amount, currency and validity. Fixed costs spread daily: monthly amount divided by days in the month.

list_products“What are we selling?”

Registered products and bundles with what a bundle is made of, and any product seen in an order that nobody has registered.

The product (SKU) registry: single SKUs and bundles with their composition, plus any unknown SKUs seen in orders that are not registered.

list_rates“What are we charging ourselves for shipping to Canada?”

Every cost rule with its scope, basis, amount, currency and dates, split into what is in force and what it replaced.

Rules are versioned rather than overwritten, so the history is here too, and the ids are what a change refers to.

Every cost rate, grouped by P&L line and component, split into active and history versions. Includes each rate's id (needed for rate_update/rate_end/rate_delete), scope (sku/country/market/channel, null = all), basis, amount, currency and validity. For pct_of_net rates the amount is a percentage.

test_cost_stack“What would we charge for a Swedish order of two units?”

The cost stack that would resolve for a product, country, market and channel you name, component by component.

It answers from the rules without touching an order, so it is the way to check a rule before anything is booked against it.

InputTypeMeaning
channel"woocommerce" | "shopify" | "amazon"
countrystring, optionalISO-2 ship country, optional
datestring, optionaldefaults to today
marketstringe.g. US, CA, GB, Others
skustring

Resolve the live cost stack for a hypothetical order line: which rate wins for every component given a SKU, ship country, market and channel. The same resolution the ledger uses (most specific rate wins).

Changing things

2 tools, and only on a connection you allowed to change costs.

apply_change“Go ahead and make that change.”

The change committed, and the ledger rebuilt so every figure moves with it.

It takes a proposal id and nothing else, so there is always a preview behind a commit; a proposal can be used once and expires after fifteen minutes.

InputTypeMeaning
proposal_idstringThe proposal_id returned by propose_change.

Commit a change that propose_change already previewed, by its proposal_id. Call this only after you have shown the person the impact and they have agreed to it. A proposal can be applied once and expires fifteen minutes after it was made; there is no way to apply a change that was not previewed first.

propose_change“What happens to my profit if shipping to Sweden goes to 22 euros?”

The change run against your real orders inside a transaction that is rolled back, and the whole P&L before and after it. Nothing is written.

This is the only way a change is ever prepared, and it can never commit. What it hands back is a proposal id.

InputTypeMeaning
change_jsonstringThe ConfigChange object as a JSON string, with an 'action' field.
summarystringOne plain-language sentence describing the change for the user's approval card.

Propose one config change to the user. This DOES NOT apply anything: the change is test-run in a rolled-back transaction and you get back its P&L impact; the user sees a card with the impact and an Apply button, and only their click commits it. Use real ids from list_rates / list_fixed_costs / get_layout / get_settings. Everything the admin UI can change is here, including the statement's own structure (sections, subtotals, the order of lines).

What a change can be

One proposal is one of the following. It is the same list the Vecks admin screens act through, so an assistant can do what you can do and nothing else. Everything about the shape of your statement is in here too: the subtotals, the sections and the order of the lines are yours, not ours.

account_map_cleartake the mapping off an account: account (its lines go back to unmapped and the months they booked come off)
account_map_setmap one bookkeeping account (as the accounting prints it on every invoice line, Netvisor) so every line past and future that carries it maps itself: account, item_key, component, or fixed_cost_id (a monthly amount instead of a charge), scope? (the rule under the charge, as on charge_actual_set), role (compare | use = books | ignore), source? (invoices = the month books the sum of the invoice lines on the account; ledger = the month books the general ledger's total on the account less what the shop's own lines explain, see item_ledger_less_set; the invoice lines are then evidence only). A vendor's own memory for a line still wins; a line a person mapped by hand is not moved
ad_rule_createan ad spend rule: conditions {platform, account, campaign_id, name_contains|name_starts|name_pattern, country ('*' known, '-' unknown, or ISO-2), tag}, targets {markets:'country'|'all'|[...], spread:'cost'|'orders'|'even'|{market:share}, product (a SKU), product_group (a group key, spread across its SKUs), cost_center_id}
ad_rule_deleteremove an ad spend rule: rule_id
ad_rule_reorderthe order of ad rules (ties in specificity): ids
ad_rule_updatechange an ad spend rule: rule_id, conditions, targets
batchseveral changes as one: changes [ ... ] (one transaction, one impact)
campaign_assignone campaign counts for a market and/or product: platform, campaign_id, market, product (a SKU) or product_group (a group key)
campaign_tag_settag a campaign for ad rules: platform, campaign_id, tag
cc_rule_createa cost center rule: warehouse/country/market/channel/item_key and conditions {sku|currency|payment_method|coupon}, cost_center_id, or shares [{cost_center_id, share}] to split
cc_rule_deleteremove a cost center rule: rule_id
cc_rule_reorderthe order of cost center rules (first match files): ids
cc_rule_updatechange a cost center rule: rule_id plus the same fields as cc_rule_create
charge_actual_setbook one month of a charge from a document instead of the rules: item_key, component, month (YYYY-MM), amount, currency, source, ref, scope? (the rule the document is about, in the rule's own fields: warehouse, country, market, channel, sku, conditions; {default: true} for the rule with no conditions; absent = the whole charge, and then only the rows no rule's document covers)
charge_hierarchy_setthe order a charge's break-outs nest in: item_key, component, hierarchy (fields outermost first)
charge_min_seta monthly minimum on a charge (the greater of the rules' total and the floor books): item_key, component, min_monthly (null removes), currency
charge_priority_setwhether documents or rules win on a charge: item_key, component, priority (documents|rules)
cost_center_createa new cost center: label, optional currency
cost_movemove a cost line into another section (across a subtotal it changes that subtotal): item_key, section_id
fixed_actual_setbook one month of a monthly amount from a document: fixed_cost_id, period_month (YYYY-MM), amount (null = the typed amount again), currency, source, source_ref, invoice_line_id
fixed_cost_center_setfile a monthly amount to a cost center: fixed_cost_id, cost_center_id
fixed_cost_createa monthly amount spread over its days: item_key, label, monthly_amount, currency, valid_from, valid_to, note
fixed_cost_deletedelete a monthly amount with every version of it: fixed_cost_id
fixed_cost_endstop a monthly amount on a date (versions after it go, history before it stays): fixed_cost_id, end_date (null re-opens it)
fixed_cost_priority_setwhen a document and the typed amount differ, which one books: fixed_cost_id, priority (documents|typed); the other is checked against it
fixed_cost_renamerename a monthly amount, every version of it: fixed_cost_id, label
fixed_cost_source_setwhere a monthly amount reads its value from: fixed_cost_id, source {kind:'sheet',tabId,cell} (re-read nightly) | {kind:'documents'} (the month's invoice line mapped to it) | null (typed). The typed amount stays as the fallback
fixed_cost_start_setthe day a monthly amount's first version starts: fixed_cost_id, valid_from (not after its next period)
fixed_cost_updatea new monthly amount from a date, history kept: fixed_cost_id, monthly_amount, effective_from
inbound_createa shipment on its way (a purchase order): sku, quantity, expected_on?, reference?, note?, warehouse? (the location it lands in; the default location when omitted, so ask which one when more than one location is open), from_country? (ISO-2, where it ships from) or from_warehouse? (a transfer from another location, which leaves that location's stock the day it ships), mode? (air | sea | train), shipped_on? (the day it left, today by default), unit_cost?, currency?, invoice_line_id? (the invoice line it came from); counts towards on hand once received
inbound_deleteremove an incoming shipment: id
inbound_receivemark a shipment received: id, received_on? (today), quantity? (what actually arrived)
inbound_updatecorrect a shipment on its way: id, and any of quantity, expected_on, shipped_on, from_country, from_warehouse, mode (air | sea | train), reference; only the fields given change
invoice_line_mapwhich charge an invoice line is evidence about: line_ids (from the invoice screen), item_key, component, or fixed_cost_id (a monthly amount instead of a charge), scope? (the rule under the charge, as on charge_actual_set; absent = the whole charge), role (compare = rules already produce this cost | use = the amount comes from the invoice | ignore). Remembered for that vendor and line description.
item_createa new cost or income line in a section: key, label, section_id
item_deletedelete a cost or income line of the merchant's own with every charge under it (lines the connectors book, such as gross sales or ad spend, cannot be deleted): item_key
item_ledger_less_setfor a line that books from the general ledger (account_map_set with source 'ledger'): the shop's lines whose booked money each month's figure subtracts, so the line is the rest of the company: item_key, less (line keys; [] books the accounts' total in full)
item_product_splithow a line's per-order money is attributed to products (shipping revenue, a fee per order): item_key, split = none (it carries no product) | units (each product's share of the units on the order) | revenue (each product's share of the order's line revenue). It moves no total, only where the money sits in the product view.
item_reorderthe order of the costs inside a section: ids (every item id in the section, in order)
market_breakoutmake a country its own market, or file it into a named one: country (ISO-2), market
market_createa market of the merchant's own: label, regions (keys from the region list, e.g. europe, nordics, north_america), countries (ISO-2). A named country beats a region; the smallest region wins; unmatched = Others
market_deleteremove a market: market_id (its countries fall to the next market or Others)
market_removefold a country back into Others: country
market_updatechange a market: market_id, label, regions, countries, except (countries the market's regions must not take), sort_order
product_group_createa product group (category) of SKUs: label, optional key, skus [sku, ...]; ad spend counting for a group is spread across its SKUs by cost share
product_group_deleteremove a product group: key (its SKUs stay, ungrouped; ad rules lose the group target)
product_group_updaterename a product group or set its members: key, label?, skus? (the full list)
rate_cost_center_setfile one rule's rows to a cost center: rate_id (+ rate_ids), cost_center_id (null = as the rules say)
rate_createa new cost rule: item_key, component (the charge), basis (per_unit|per_order|pct_of_net|per_refund), amount, currency, valid_from, optional scope sku/warehouse/country/market/channel, conditions {currency|payment_method|coupon}, stacks (a rule on top), note
rate_deletedelete a rule and its history as if it never existed: rate_id (+ rate_ids), valid_from (the rule's own)
rate_endstop a rule from a date, history kept: rate_id (+ rate_ids), end_date (the last valid day)
rate_source_setread a rule's value from somewhere: rate_id, source {kind:'sheet',tabId,cell} | {kind:'connector',field} | null (typed); a connector field is 'amazon.fee_total' or a number a WooCommerce plugin keeps on the order, 'woocommerce.meta:<key>' (e.g. 'woocommerce.meta:_stripe_fee', nested 'woocommerce.meta:_ppcp_paypal_fees/paypal_fee/value'), on a per_order or pct_of_net rule; an order without the figure books the typed amount
rate_updatea new value for a rule from a date, history kept: rate_id (+ rate_ids for a combined row), amount, effective_from, optional note, source
section_assignmove a section under another subtotal: section_id, subtotal_id
section_createa new section (a group of lines under a subtotal): label, stage, optional subtotal_id, optional parent_id (inside another section)
section_deletedelete an empty section: section_id
section_reorderthe order of sections: ids
section_updaterename, restage or nest a section: section_id, label and/or stage, sort_order, parent_id (null lifts it to the top level)
sku_group_setput one SKU in a group, or take it out: sku, group_key (null = none)
sku_renamerename a product wherever it is shown: sku, label (a name only, nothing is rebuilt)
sku_upsertregister or rename a product: sku, label, kind (single|bundle), bundle_components for a bundle
stock_countrecord a stock count for a single product (the stock at the end of that day; on hand is derived from the newest count and the ledger's sales since): sku, quantity, counted_on, note?, warehouse? (a storage location key; the default location when omitted)
stock_count_deleteremove a stock count: id (from get_inventory)
subtotal_createa new subtotal line of the merchant's own (EBITDA, Net revenue): label
subtotal_deletedelete a subtotal that holds no sections: subtotal_id
subtotal_reorderthe order of subtotals: ids (the opening one stays first)
subtotal_updaterename or reorder a subtotal, Gross profit and Net profit included: subtotal_id, label, sort_order

Without a sign-in

Some assistants take a header instead of signing in. For those, an owner can make a token under Configure, Assistants. It is shown once and stored only as a hash, so a copy of our database is not a usable credential. In Claude Code:

claude mcp add --transport http vecks https://vecksapp.com/api/mcp \
  --header "Authorization: Bearer <your token>"

A token made this way does not expire, which is why the sign-in path is the better one where a client offers it: those tokens are short and refresh themselves, and either can be revoked from the same panel.

If it does not work

Privacy

A connected assistant reaches your company's figures and nothing outside them, under the address that approved it. It cannot reach another company, and it cannot reach anything you could not reach yourself. What the connectors store is described in the privacy policy: order payloads are stripped of personal data before they are stored, so what an assistant can read about an order is money, products, country and dates.