Sign inComing soon

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:

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

ToolWhat it does
get_accountYour tier, access state, report quota, markets allowed, trial end date and the features your plan includes.
list_marketsThe (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_marketAdds 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_marketDrops 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_marketsAfter a plan change, chooses which markets stay active.
get_rates / set_ratesYour own prices per service, which the outreach kit turns into a quote.
get_digest_preview / set_digestWhat this week's digest email would say (subject and plain text), and turning it on or off.

Reports

ToolWhat it does
run_reportRuns 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_reportRuns 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_reportLists 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_reportThe same for market reports. A reopened report follows your plan now.
share_report / list_shares / revoke_shareMakes, lists and turns off read-only client links to a report.
download_reportsA 15-minute link to a zip of up to 50 reports and their toolkits.

Business facts and outcomes

ToolWhat it does
set_business_factsSaves 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_factsReads a business's saved profile and facts.
check_business_factsGrowth. Checks AI answers against the saved facts. Up to 5 checks per business per day.
log_outcome / list_outcomes / delete_outcomeGrowth. Record a change you made for a business, see its measured before/after effect, or delete one logged by mistake.

Agency

ToolWhat it does
engine_breakdownHow often each AI engine names a business.
answer_evidenceThe AI answers behind a business's numbers: question, excerpt, sources.
what_worksPooled, anonymized results of the changes subscribers made.
request_marketFlag a market you want covered without adding it to your account.
list_clients / add_client / update_client / delete_clientThe client roster. add_client with status prospect adds a business you're pitching.
client_status / run_client_reportsMonitoring, and one report per active client (up to 25 per call).
export_marketA market's businesses or sites as CSV or JSON. Free: no report quota.
get_branding / set_brandingWhite-label name, logo and colour on share and plan links. Only the fields you pass change; null or "" clears one.
list_membersWho 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.

ToolWhat it does
get_outreach_kitFirst 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_kitEach directory AI reads that the business is missing from, how to claim it, and copy to paste.
get_fix_packageWebsite files for the checks the site failed, with install steps.
get_pitch_draftsA pitch per local-news or "best of" site AI reads. Starter: the top outlet on every report. Growth: all.
get_action_plan / update_plan_itemThe steps in order, and marking them done or skipped. On Growth, done logs an outcome so results are measured.
get_correction_kitGrowth. FAQ text and listing fixes for what the fact check found AI getting wrong.
get_attribute_gapAgency. Qualities AI credits competitors with but not this business, checked against its facts.
write_prospect_outreachAgency. Outreach drafts for every prospect on your roster with a recent report, up to 10 new ones per call.
check_client_sitesAgency. 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:

Keep in mind