Connect your AI tools to Algotally
ChatGPT and Claude.ai connect to the MCP server at /mcp by signing in. Other tools and the REST API use an API key: create one in Settings (sign in first). Either way it's the same markets and report quota as the app.
Your Algotally account works from inside the AI tools you already use. ChatGPT, Claude.ai and other apps that support MCP sign-in connect with no key at all (section 0). For Claude Desktop, Claude Code, Cursor and scripts, create an API key once, paste it in, and ask the assistant to run a report. Everything a key does counts against the same market and report quota as your account — there is no separate "API plan".
Base URL: https://algotally.com
0. Connect by signing in (ChatGPT, Claude.ai)
Add a custom connector with the address https://algotally.com/mcp:
- Claude.ai: Settings → Connectors → Add custom connector.
- ChatGPT: Settings → Apps & Connectors → Create (with developer mode on).
- Any other client that supports MCP authorization (OAuth 2.1 with dynamic registration or a Client ID Metadata Document) works the same way.
The app sends you to Algotally. Sign in if you aren't already, check which app is asking and where you'll be sent back, then Allow. Leave "run reports and make changes" ticked to let it use your quota and change account data, or untick it for read-only access; a view-only seat can only connect read-only. A read-only connection that tries a tool that changes something is asked to reconnect with write access.
Each connection belongs to the seat that approved it. Settings → Connected apps lists them (the owner and admin seats see every seat's) and disconnects one at once. Removing a seat disconnects its apps too.
1. Create an API key
Only the account owner, signed in, can create or revoke keys. A team seat can't, and a key can't create other keys — that's deliberate, so a leaked key can't multiply and a key never outlives the seat of the person who made it.
The easy way: sign in at https://algotally.com/app/, open Settings → API keys, and create one.
From a script, you need a session token first. A sign-in link looks like …/app/#/verify?token=…; post that token to get a session (links work once, for 15 minutes):
curl.exe -X POST -H "Content-Type: application/json" -d "{\"token\":\"TOKEN_FROM_YOUR_LINK\"}" https://algotally.com/api/v1/platform/verify
That returns { "session_token": "…", "account": { … } }. Then:
curl.exe -X POST -H "Authorization: Bearer YOUR_SESSION_TOKEN" -H "Content-Type: application/json" -d "{\"label\":\"Claude Desktop\"}" https://algotally.com/api/v1/platform/api-keys
The response contains your key once:
{ "id": 1, "label": "Claude Desktop", "key_prefix": "a1b2", "created_at": "…", "last_used_at": null, "key": "si_live_…", "note": "Store this key now — it is not shown again." }
Despite its name, key_prefix is the key's last 4 characters: the part you can match against the key you stored. Store the key somewhere safe. You can have up to 5 active keys (a 6th is a 409); list them with GET /api/v1/platform/api-keys (session or key) and revoke one with DELETE /api/v1/platform/api-keys/{id} (owner session only).
2. Add Algotally to your AI tool
The MCP endpoint is https://algotally.com/mcp (a trailing slash works too). It expects your key in the Authorization header as Bearer si_live_…. A refused key says why, with a code: session_token (a sign-in token from the app: use a key, or connect by signing in as in section 0) or unknown_key (revoked or mistyped). A request with no key gets the standard MCP sign-in challenge (401 with WWW-Authenticate pointing at /.well-known/oauth-protected-resource/mcp), which is how sign-in clients find their way. Opening the address in a browser shows what it's for.
Claude Code (one command):
claude mcp add --transport http algotally https://algotally.com/mcp --header "Authorization: Bearer si_live_YOUR_KEY"
Cursor — add to .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):
{
"mcpServers": {
"algotally": {
"url": "https://algotally.com/mcp",
"headers": { "Authorization": "Bearer si_live_YOUR_KEY" }
}
}
}
Claude Desktop — add to claude_desktop_config.json (Settings → Developer → Edit Config). Claude Desktop reaches remote servers through the mcp-remote bridge, which passes the header for you:
{
"mcpServers": {
"algotally": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://algotally.com/mcp",
"--header", "Authorization: Bearer si_live_YOUR_KEY"
]
}
}
}
Restart the app after saving. Algotally should appear in the tools list.
3. What the assistant can do
Account and markets
| Tool | What it does |
|---|---|
get_account | Your tier, access state, report quota, markets allowed, trial end date and the features your plan includes. |
list_markets | The (industry, city, state) markets you can report on, whether each is still building, recently removed markets, your market swaps this month, and over_cap after a plan change. |
add_market | Adds a market, up to your tier's cap. A market nobody covers yet is built on the spot (10–20 seconds). A near-miss name returns suggestions; confirm_new adds it as typed. dry_run shows what would happen: would_fail means every slot is in use, so use swap_market. |
remove_market / swap_market | Drops a market, or replaces one with another in one step. Uses a market swap (you get as many a month as you have market slots). dry_run shows the cost first. |
keep_markets | After a plan change, chooses which markets stay active. |
get_rates / set_rates | Your own prices per service, which the outreach kit turns into a quote. |
get_digest_preview / set_digest | What this week's digest email would say (subject and plain text), and turning it on or off. |
Reports
| Tool | What it does |
|---|---|
run_report | Runs a business report: how often AI names the business, its rank, the sites AI cites in that market and whether it's listed on each. Spends one report run, unless no new AI answers have come in since your last run (then it reopens that one free: reused: true). |
run_market_report | Runs a market report for one industry in one city: every source AI cites there, the businesses AI recommends most, how concentrated recommendations are, and what AI credits businesses with. Spends one report run, or reopens free when nothing changed. |
list_reports / get_report | Lists past business reports a page at a time (id, business, market, date; include_result adds each stored result; limit up to 100, default 20; pass next_before_id back as before_id), and reopens one in full, without spending quota. |
list_market_reports / get_market_report | The same for market reports. A reopened report follows your plan now. |
share_report / list_shares / revoke_share | Makes, lists and turns off read-only client links to a report. |
download_reports | A 15-minute link to a zip of up to 50 reports and their toolkits. |
Business facts and outcomes
| Tool | What it does |
|---|---|
set_business_facts | Saves a business's profile (website, phone, address, services, area, hours) and knowledge catalog (online booking page, price list, customer questions with answers) on every tier; price range, founding year and other facts are Growth. Fields left out keep their saved value; an empty string clears one. |
get_business_facts | Reads a business's saved profile and facts. |
check_business_facts | Growth. Checks AI answers against the saved facts. Up to 5 checks per business per day. |
log_outcome / list_outcomes / delete_outcome | Growth. Record a change you made for a business, see its measured before/after effect, or delete one logged by mistake. |
Agency
| Tool | What it does |
|---|---|
engine_breakdown | How often each AI engine names a business. |
answer_evidence | The AI answers behind a business's numbers: question, excerpt, sources. |
what_works | Pooled, anonymized results of the changes subscribers made. |
request_market | Flag a market you want covered without adding it to your account. |
list_clients / add_client / update_client / delete_client | The client roster. add_client with status prospect adds a business you're pitching. |
client_status / run_client_reports | Monitoring, and one report per active client (up to 25 per call). |
export_market | A market's businesses or sites as CSV or JSON. Free: no report quota. |
get_branding / set_branding | White-label name, logo and colour on share and plan links. Only the fields you pass change; null or "" clears one. |
list_members | Who else can sign in. Adding or removing a seat needs the owner signed in to the app. |
Business tools. Each works on one report (report_run_id from run_report), stays open for 60 days after the report, and never spends report quota.
| Tool | What it does |
|---|---|
get_outreach_kit | First email, follow-up, phone opener and "what I found" summary for a prospect, written only from the report's findings, plus the work found, published market rates for reference and a quote in your own rates. You send it from your own email. |
get_listing_kit | Each directory AI reads that the business is missing from, how to claim it, and copy to paste. |
get_fix_package | Website files for the checks the site failed, with install steps. |
get_pitch_drafts | A pitch per local-news or "best of" site AI reads. Starter: the top outlet on every report. Growth: all. |
get_action_plan / update_plan_item | The steps in order, and marking them done or skipped. On Growth, done logs an outcome so results are measured. |
get_correction_kit | Growth. FAQ text and listing fixes for what the fact check found AI getting wrong. |
get_attribute_gap | Agency. Qualities AI credits competitors with but not this business, checked against its facts. |
write_prospect_outreach | Agency. Outreach drafts for every prospect on your roster with a recent report, up to 10 new ones per call. |
check_client_sites | Agency. Checks roster clients' websites now (also weekly) and lists alerts. |
Drafts are written once and stored; refresh rewrites one, up to 3 times. A draft written before the business's facts changed comes back with stale: true when it can't be rewritten.
Every tool is marked read-only, write or destructive (MCP tool annotations), so a client can run read-only ones without asking and confirm destructive ones (removing or swapping a market, revoking a share, deleting a client or an outcome) first. Results are compact JSON; the main tools put a one-line summary first. Rates are shares of AI answers from 0 to 1, always with how many answers they rest on; a verdict of too_few_answers means there isn't enough data to call a change, and within_noise means the change is smaller than run-to-run variation. Market names match ignoring case ("houston", "party rentals").
Tier-limited tools answer with the plan that includes them, its price and your current plan, e.g. "This needs the Growth plan ($99/month). Your plan is Starter.", with code feature_locked. The same message comes back as HTTP 402 from the REST API. When your trial has ended or your plan is canceled, the account is read-only: reading tools keep working, and tools that spend (reports, new markets, AI drafts, share links, facts checks, downloads) answer with an error mentioning plan_inactive. get_account shows which state you're in.
Try: "Run an Algotally report for Acme Plumbing in Houston plumbing and tell me the three most important places they're missing." Then: "Write the outreach kit for it."
4. Plain HTTP, if you'd rather script it
The data routes accept either the key or a session token, so a shell script or a custom GPT action works the same way. The exceptions are creating and revoking keys and adding or removing seats: those need the owner's session (403, code: "owner_only", otherwise).
curl.exe -X POST -H "Authorization: Bearer si_live_YOUR_KEY" -H "Content-Type: application/json" -d "{\"business_name\":\"Acme Plumbing\",\"industry\":\"plumbing\",\"city\":\"Houston\"}" https://algotally.com/api/v1/platform/reports
All bodies are JSON. Everything is under /api/v1/platform.
# Signing in (no credential needed)
GET /plans # tiers, prices, quotas and features → { plans: [...] }
POST /signup { email, tier?, industry?, city?, state?, mode?: "signup"|"login" }
# always { message } — a sign-in link goes out if the address is valid.
# mode "login" only sends to an existing account or seat and never creates one.
# A new account starts a 14-day trial of the tier chosen (default starter),
# with 3 reports.
POST /verify { token } # redeem a sign-in link → { session_token, account }
# (GET /verify?token= only redirects to the app's verify page)
# Session only
POST /logout # ends this session
POST /logout-all # ends every session (the owner: the whole account; a seat: its own)
# Account
GET /account # tier, access, quota, features, trial_ends_at, digest_enabled,
# prepared_by, is_owner, member_email
PATCH /account { prepared_by: string | null } # default "Prepared by" on share links (Growth)
# Markets
GET /markets # { count, markets, removed_markets, swaps, over_cap }
POST /markets { industry, city, state?, confirm_new?, dry_run? }
# → { action, market, matched_from, seed, markets, count }; 201 when added.
# action: already_held | granted | seeded | building | would_grant | would_seed.
# state is required for a market nobody covers yet.
POST /markets/remove { industry, city, dry_run? }
POST /markets/swap { remove: { industry, city, state? }, add: { industry, city, state?, confirm_new? }, dry_run? }
POST /markets/keep { markets: [{ industry, city }, …] }
# Reports (each run spends one report)
POST /reports { business_name, industry, city, state?, business_url? }
# → the report plus report_run_id, reports_used_this_period, reports_allowed_per_period
GET /reports # ?limit= (default 20, max 100) &before_id=
# → { count, total, reports, next_before_id }; newest first, next_before_id is
# null on the last page. Each row carries result_json and site_health_json
# as JSON strings.
GET /reports/:id # reopen one stored business report (free), gated for your plan now
POST /market-reports { industry, city, state? }
GET /market-reports # { count, reports: [{ id, industry, city, run_at }] }, newest 200
GET /market-reports/:id
# Share links and digest
POST /reports/:id/share { prepared_by?, days? } # → 201 { id, url, expires_at }
POST /market-reports/:id/share { prepared_by?, days? }
POST /reports/:id/plan/share { prepared_by?, days? } # Agency: white-label plan link
GET /shares # { shares }
DELETE /shares/:id
GET /digest/preview # { digest } — null when there's nothing to report
POST /digest { enabled: boolean }
# Business facts, rates, outcomes
PUT /facts { business_name, industry, city, website_url?, phone?, address?, services?, service_area?,
hours?, booking_url?, prices?: [{ service, price }], faqs?: [{ question, answer }],
price_range?, year_founded?, other_facts? }
# fields left out keep their value; "" clears one, year_founded: null clears it.
# prices (up to 50) and faqs (up to 30) replace the saved lists; [] clears them.
# price_range, year_founded and other_facts are Growth.
GET /facts?business_name=&industry=&city=
POST /facts/check { business_name, industry, city } # Growth; 5 model checks per business per day
GET|PUT /rates { rates: [{ service, amount | null }] } # GET adds services and disclaimer
POST /outcomes { business_name, industry, city, action, host?, note?, done_on? } # Growth → 201 { id }
GET /outcomes # { count, outcomes }
DELETE /outcomes/:id
# Business tools: :id is a report_run_id from the last 60 days; none use report quota
GET /reports/:id/tools/outreach-kit # { drafts, pricing }; ?drafts=0 read only, ?refresh=1 rewrite
GET /reports/:id/tools/listing-kit
GET /reports/:id/tools/fix-package
GET /reports/:id/tools/pitches # ?drafts=0, ?host=chron.com, &refresh=1
GET /reports/:id/tools/correction-kit # Growth; ?drafts=0, ?refresh=1
GET /reports/:id/tools/attribute-gap # Agency
GET /reports/:id/plan
PATCH /reports/:id/plan/:item_key { status: todo|done|skipped }
GET /reports/:id/download # one report's toolkit as a zip
POST /reports/download { report_run_ids: [..up to 50] } → zip
GET /downloads/:token # a download_reports link; the token is the credential
# API keys
POST /api-keys { label? } # owner session only → 201 with the key, once
GET /api-keys # { count, keys: [{ id, label, key_prefix, created_at, last_used_at }] }
DELETE /api-keys/:id # owner session only
# Agency tier
GET /engines?business_name=&industry=&city= # per-engine breakdown
GET /evidence?business_name=&industry=&city=&limit= # the AI answers behind the numbers
GET /export?industry=&city=&dataset=businesses|sites&format=csv|json # uses 1 report
GET /insights # pooled "what works"
POST /market-requests { industry, city, state }
GET /clients # { clients }
POST /clients { business_name, industry, city, business_url?, notes?, status? }
PATCH /clients/:id { status?: active|paused|churned|prospect, notes?, business_url? }
DELETE /clients/:id
GET /clients/status # monitoring: where each active client stands (free)
POST /clients/bulk-report # one report per active client, up to 25
POST /clients/outreach-kits # outreach drafts for prospects, up to 10 new per call
POST /clients/site-check # check active clients' websites now
GET|PUT /branding { display_name, logo_url (https), accent_color (#rrggbb) }
# PUT changes only the fields sent; null or "" clears one
GET /members # { members }
POST /members { email } DELETE /members/:id # owner session only
Read-only accounts. When a trial has ended (access: "trial_expired") or a plan is canceled (access: "canceled"), you can still sign in, read everything and change settings: account, facts, digest off, markets/keep, revoking share links and keys, deleting outcomes and removing seats. Anything that spends answers 402 with code: "plan_inactive": running reports, adding or swapping markets, writing AI drafts (the tool GETs unless ?drafts=0), downloads, exports, share links and the other writes.
Errors come back as { "error": "…" }, sometimes with a code or a hint for scripts:
400: a missing or malformed field, a sign-in link that's invalid, expired or already used, orcode: "state_required"when a city is in more than one of your states (the body listsstates; resend withstate).401: no credential, or a bad, revoked or expired one.402: report quota used up, a feature or market count your tier doesn't include, no market swaps left this month, orcode: "plan_inactive"(read-only account; the body also hasaccess).403: that market isn't one of your active markets (with ahint), orcode: "owner_only"for key and seat management.404: no such report, share, client, outcome, key or fact sheet on your account.409: already 5 active API keys, or a market name close to one already covered (the body listssuggestions; resend withconfirm_new: trueto add it as typed).410: the report is more than 60 days old (or older than the tools), so its tools are closed. Run a new one.422: a new market's first answers named no businesses, so nothing was added; or the writing model declined to write a draft.429:code: "rate_limited"(too many sign-up or sign-in attempts; wait a minute),code: "signup_cap"(the day's limit on new accounts), 5 fact checks for that business today, a draft already rewritten 3 times (or rewriting paused for the month), or the day's limit on building new markets (5 per account).500: something went wrong on our side. Try again in a minute.502: the writing model failed or wrote something unusable. Try again; nothing was used up.
Keep in mind
- A key is a credential for your account. Treat it like a password; revoke it if it leaks. Canceling an account revokes all its keys.
- Reports generated with a key are yours to deliver to your clients. Bulk-pulling the underlying data to build a separate database isn't allowed — see the Terms of Service.
- Algotally never contacts businesses on your behalf. It gives you the data and writes drafts; sending them is yours, from your own email, and so is following CAN-SPAM and other outreach laws (fill in the mailing address and opt-out placeholders in every email).
- Market rates in the outreach kit are published figures for reference. Algotally doesn't suggest what you should charge.