Announcing CIMD support for MCP Client registration
Learn more

Bitly MCP vs Bitly API for AI Agents (2026)

TL;DR

  • Bitly's hosted MCP server accepts two auth methods: OAuth 2.1 with Dynamic Client Registration, and a static Bitly API token passed as a bearer header. Unlike the Notion, Slack, and Salesforce MCP servers, Bitly's does not force an interactive browser flow, so a headless agent can call MCP directly.
  • The published tool count is unreliable. Bitly's marketing page cites 27 tools, its own MCP Tools Reference enumerates 31, and Scalekit's Bitly MCP connector enumerates 53. Enumerate the surface at runtime; do not build against a docs page.
  • The real capability gap is not link CRUD or analytics, both of which MCP covers well. It is webhooks, campaigns and channels, bulk tag and archive operations, CSV exports, and QR code deletion. Those are REST-only, and they are exactly what event-driven and cleanup agents need.
  • Bitly's documented v4 OAuth web flow returns access_token and login. No expires_in, no refresh_token, no scope parameter. A credential that never expires is not a simpler lifecycle; it is a revocation problem with no expiry backstop.
  • Scalekit's Bitly MCP connector resolves the per-user credential on every tool call, keeps it out of agent runtime and LLM context, and logs each call against the user who authorized it. The MCP versus API decision does not change what you need at the credential layer.

Your agent needs to shorten links, tag them to a campaign, and report back on clicks and scans. Bitly ships a hosted MCP server at api-ssl.bitly.com/v4/mcp and a v4 REST API that has been in production for years. Most tool-by-tool comparisons in this space end with "MCP is OAuth-only, so headless agents need the API." Bitly breaks that pattern: its MCP server accepts a static API token as a first-class auth method. That changes which path you pick, and it changes what you have to build underneath it.

What Bitly MCP and Bitly API actually are

These are two entry points into the same Bitly platform, maintained by the same team, with different shapes. One is a managed tool surface built for LLM consumption. The other is the versioned REST surface that Bitly's own apps run on. Knowing which object you are pointing your agent at matters more than it usually does here, because Bitly's MCP server is unusually permissive about auth.

Bitly MCP

The Bitly MCP Server is Bitly's official hosted endpoint at https://api-ssl.bitly.com/v4/mcp, using HTTP transport. Per Bitly's MCP changelog, it shipped in August 2025 with core link, analytics, and QR tools; added six QR scan metrics tools in October 2025; and added OAuth 2.1 with Dynamic Client Registration in December 2025, at which point all MCP endpoints began requiring authentication.

Two auth methods are supported. OAuth 2.1 with DCR is the recommended path and handles token renewal without manual intervention. An API token from your Bitly account settings, passed as Authorization: Bearer, works for clients without OAuth support. Full setup details are in the Bitly MCP Server quickstart.

Bitly API

The Bitly v4 REST API exposes nine resource groups: Bitlinks, BSDs, Campaigns, Custom Bitlinks, Groups, Organizations, QR Codes, User, and Webhooks. Everything the MCP server does maps onto it, plus a substantial surface the MCP server does not touch.

Auth accepts a generic access token from account settings, an OAuth 2.0 access token from the web flow, or a token obtained through the resource owner credentials grant. The Bitly authentication guide documents all three. Note the shape of the OAuth web flow response, because it drives most of what follows.

Comparing them where it matters for agents

Four dimensions decide this: what your agent can call, what credential it holds, what breaks in production, and which workload each path suits. Bitly is a smaller platform than Salesforce or GitHub, so the capability gap is narrower than you might expect and the operational constraints matter proportionally more.

What your agent can actually do

The MCP server covers link creation, link updates, QR code creation and customization, link-level analytics, group-level analytics, and account structure. For a marketing agent that shortens, tags, and reports, that is close to complete coverage.

The gap opens on write operations at scale, on lifecycle management, and on anything event-driven.

Capability
Bitly MCP
Bitly REST API
Create and customize short links
Yes
Yes
Update title, tags, destination, archive state
Yes
Yes
Delete a short link
Yes (unedited links only)
Yes (unedited links only)
Create, update, retrieve QR codes
Yes
Yes
Link-level click, engagement, geo, device, referrer metrics
Yes
Yes
QR scan metrics by country, city, device OS, browser
Yes
Yes
Group-level click, scan, and engagement rollups
Yes
Yes
Bulk upload links or QR codes from CSV or XLSX
Yes (enterprise plan)
Yes
Bulk tag or archive up to 100 existing links or QR codes
No
Yes
Delete a QR code
No
Yes
Campaigns and channels
No
Yes
Move a custom back-half between links
No
Yes
CSV export of links, QR codes, or engagement batches
No
Yes
Webhooks for link engagement events
No
Yes
Plan limit and platform limit introspection
No
Yes

The gap that actually bites

Two rows on that table decide real architectures. The first is bulk update. Bitly's REST API exposes PATCH /v4/groups/{group_guid}/bitlinks, which archives or edits tags on up to 100 links in a single call. The MCP server has no equivalent, so a retagging agent has to loop bitlymcp_update_short_link one link at a time.

The second is webhooks. Bitly's API reference includes a Webhooks resource for pushing link engagement events out of Bitly. There is no MCP tool for it. If your agent reacts to click activity rather than polling for it, that logic lives on the REST side regardless of what the rest of your agent uses.

The tool count nobody agrees on

Here is a concrete reason not to trust any static tool list for this connector. Bitly's MCP landing page says 27 tools. Bitly's own MCP Tools Reference enumerates 31 across six categories. Scalekit's Bitly MCP connector docs enumerate 53.

The 53 is not inflation. It includes the six QR scan metrics tools the changelog says shipped in October 2025 and the group-level analytics tools, neither of which appear in the published Tools Reference. The reference page is behind the live server.

What this means for your build

Treat the tool surface as runtime state, not documentation. Enumerate it per user before each run and pin the tool names your agent depends on, so a server-side addition does not silently widen what your agent can reach.

This is also why MCP tool schemas are a weaker dependency contract than REST endpoints here. Bitly's v4 API is versioned and stable; the MCP surface has grown three times in the documented changelog and the docs have not kept pace.

The auth path each one puts you on

Bitly is the exception in this series. Its hosted MCP server does not require an interactive OAuth flow, which removes the usual headless blocker.

Dimension
Bitly MCP
Bitly REST API
Interactive OAuth
OAuth 2.1 with DCR, automatic token renewal
OAuth 2.0 web flow, authorization_code
Static credential
Account API token as bearer header
Generic access token as bearer header
Non-interactive alternative
None beyond the static token
Resource owner credentials grant
Scope granularity
Whole-account, inherited from the user
Whole-account, inherited from the user

Why the static token is a trap in multi-tenant agents

The API token path is the fastest way to a working demo and the wrong default for a B2B product. One token means every user's agent acts as whoever generated it. Link ownership, creator attribution in created_by, and group membership all collapse to that single identity.

Shared credentials are a single-user solution. They do not survive a second user. What the user cannot do, the agent should not be able to do either, and a shared token makes that guarantee impossible to state. This is the core argument covered in depth in OAuth vs API Keys for AI Agents: Why Static Credentials Break in Production Systems.

The credential that never expires

Look at what Bitly's documented OAuth web flow actually returns: access_token=%s&login=%s. There is no expires_in, no refresh_token, and no scope parameter in the documented response.

Teams read that as one less thing to build. It is the opposite. Proactive refresh is not available as a safety net, so revocation inside Bitly's settings is the only lifecycle event, and your agent learns about it when a call starts failing. A token generated eight months ago by an employee who has since left is still valid until someone revokes it explicitly. Understanding how to handle token refresh for AI agents becomes even more important when the underlying platform gives you no expiry signal to rely on.

What you own in production

Bitly manages the MCP server, its tool schemas, and endpoint normalization. On the REST path you own request construction, pagination, retries, and versioning. Standard tradeoff. Two Bitly-specific constraints apply to both paths equally, and they surprise people.

Rate limits apply to MCP calls too

Bitly's rate limits come in two layers. Platform limits apply to every account: a maximum of five concurrent connections from a single IP address, per-endpoint hourly caps, and a per-minute cap equal to one tenth of the hourly cap. Exceeding them returns a 429 with RATE_LIMIT_EXCEEDED.

Plan limits are monthly and, critically, apply across all transactions in the API and the Bitly apps. Exceeding the monthly API request allowance returns a 429 with API_USAGE_LIMIT_EXCEEDED. Bitly does not publish per-endpoint hourly figures; you read your own via GET /v4/user/platform_limits and GET /v4/organizations/{organization_guid}/plan_limits.

The five-connection IP ceiling

That IP limit deserves its own paragraph because it is an infrastructure constraint disguised as an API constraint. A containerized agent fleet behind a single NAT egress IP shares those five concurrent connections across every tenant it serves. Agentic workflows issue several sequential calls per user action, so twenty concurrent agent runs do not get twenty connections. Plan egress accordingly, and monitor limit usage from day one rather than after the first 429 storm.

When to use MCP, when to use the API

Use Bitly MCP when:

  • Your agent's job is create, read, and report: shortening campaign URLs, generating branded QR codes, and pulling click and scan breakdowns in natural language
  • You want Bitly to maintain the tool schemas rather than writing and versioning your own against nine REST resource groups
  • Your agent is interactive and runs inside Claude Code, Cursor, or a chat assistant where OAuth consent fits the flow
  • Your agent orchestrates Bitly alongside other tools and you want one tool-calling convention across all of them

Use the Bitly REST API when:

  • Your agent retags, archives, or cleans up links in batches; PATCH /v4/groups/{group_guid}/bitlinks handles 100 at a time, and the MCP loop equivalent will burn your monthly API allowance
  • Your agent reacts to link engagement events through webhooks instead of polling analytics endpoints
  • Your workflow touches campaigns, channels, custom back-half reassignment, CSV exports, or QR code deletion
  • You need plan and platform limit introspection inside the agent to throttle itself before Bitly does it for you
  • Schema stability matters more than automatic tool updates

Building a Bitly agent with Scalekit

Scalekit ships a Bitly MCP connector that wraps Bitly's hosted MCP server behind a per-user connected account. The agent never holds a Bitly credential; Scalekit resolves it server-side at call time and injects it into the upstream request.

One prerequisite before any code runs: the connection_name you pass in code must match the connection name configured in your Scalekit dashboard exactly. This is the single most common integration error.

Note on the credential mode

Scalekit's connector docs list the Bitly MCP connector under OAuth 2.1 with DCR, while the Bitly MCP connector page describes API-token storage. Bitly's server accepts both, so confirm which mode your environment is configured for in the dashboard before you ship.

Authorize the user

Every user authorizes Bitly once. Scalekit returns a time-limited link, the user completes the flow, and a connected account is created and bound to that identifier.

import os from dotenv import load_dotenv from scalekit.client import ScalekitClient load_dotenv() scalekit = ScalekitClient( env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) actions = scalekit.actions # connection_name must match the connection configured in your Scalekit dashboard CONNECTION_NAME = "bitlymcp" IDENTIFIER = "marketer@acme.com" magic_link = actions.get_authorization_link( connection_name=CONNECTION_NAME, identifier=IDENTIFIER, user_verify_url="https://your-app.com/verify", ) print("Authorize Bitly:", magic_link.link)

Retrieve the authorized tool surface

Before the agent sees anything, retrieve the tools this user's connected account is authorized to call. This is not exploration of an unknown surface; it is a scoped, deterministic list derived from what this specific user granted.

The scoping matters numerically here. Scalekit's Bitly MCP connector exposes 53 tools, of which roughly 40 are analytics reads. At Scalekit's own estimate of about 200 tokens per tool definition, handing the agent the full connector costs around 10,600 tokens of context before it does any work. A link-reporting agent needs five.

scoped = actions.tools.list_scoped_tools( identifier=IDENTIFIER, page_size=100, ) print(f"{len(scoped.tools)} tools authorized for {IDENTIFIER}")

Execute a tool

Once the connected account is active, execute_tool runs a named tool against it. Scalekit fetches the credential, calls Bitly, and returns structured output. The token never enters your process.

result = actions.execute_tool( tool_name="bitlymcp_create_short_link", tool_input={ "long_url": "https://acme.com/q3-product-launch", "title": "Q3 Launch: LinkedIn", "tags": ["q3-launch", "linkedin"], }, identifier=IDENTIFIER, ) print(result.data)

Wiring it into LangChain

With auth handled, the agent loop is ordinary. Scalekit's LangChain adapter returns native StructuredTool objects filtered to the connector and tool names you specify, so the model sees a five-tool surface instead of fifty-three.

Surface reduction is the lever on tool-calling accuracy. A stronger model choosing from 53 Bitly tools, 40 of which are near-identical analytics reads differing only by dimension, still underperforms a correctly scoped surface. Model upgrades help. They are not the lever. For a deeper look at how LangChain tool calling works and where it stops, that pattern is covered in detail separately.

The scoped LangChain agent

import os from dotenv import load_dotenv from scalekit.client import ScalekitClient from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent load_dotenv() scalekit = ScalekitClient( env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) actions = scalekit.actions IDENTIFIER = "marketer@acme.com" # Only the tools this campaign-reporting agent needs, scoped to this user tools = actions.langchain.get_tools( identifier=IDENTIFIER, connection_names=["bitlymcp"], # must match your dashboard connection name tool_names=[ "bitlymcp_get_groups", "bitlymcp_get_group_short_links", "bitlymcp_get_group_links_clicks_top", "bitlymcp_link_clicks_summary", "bitlymcp_link_referrers", ], page_size=100, ) model = ChatOpenAI(model="gpt-4o", temperature=0) agent = create_react_agent(model, tools) response = agent.invoke({ "messages": [{ "role": "user", "content": ( "Find my default Bitly group, then tell me the five links with the " "most clicks in the last 30 days and the top referrer for each." ), }] }) print(response["messages"][-1].content)

Wiring it into the Claude SDK with TypeScript

If you would rather drive the loop yourself, listScopedTools returns raw JSON Schema you can pass straight to the Anthropic Messages API. The pattern is the same: retrieve the authorized surface, filter it, run the loop, route each tool_use block back through executeTool.

Retrieving and mapping the tools

import { ScalekitClient } from "@scalekit-sdk/node"; import Anthropic from "@anthropic-ai/sdk"; import "dotenv/config"; const scalekit = new ScalekitClient( process.env.SCALEKIT_ENVIRONMENT_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET!, ); const anthropic = new Anthropic(); const IDENTIFIER = "marketer@acme.com"; // connectionNames must match the connection name in your Scalekit dashboard const { tools } = await scalekit.tools.listScopedTools(IDENTIFIER, { filter: { connectionNames: ["bitlymcp"], toolNames: [ "bitlymcp_create_short_link", "bitlymcp_create_short_link_with_qr", "bitlymcp_link_clicks_summary", "bitlymcp_get_custom_domains", ], }, pageSize: 100, }); const claudeTools = tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.inputSchema, }));

The full agent loop

Nothing is elided here. The loop runs until Claude stops requesting tools.

const messages: Anthropic.MessageParam[] = [ { role: "user", content: "Create a branded short link with a matching QR code for " + "https://acme.com/webinar-oct, title it 'October Webinar', " + "and tag it 'webinar'. Then tell me which custom domains I could have used.", }, ]; while (true) { const response = await anthropic.messages.create({ model: "claude-sonnet-4-6", max_tokens: 2048, tools: claudeTools, messages, }); messages.push({ role: "assistant", content: response.content }); if (response.stop_reason !== "tool_use") { const text = response.content .filter((block) => block.type === "text") .map((block) => (block as Anthropic.TextBlock).text) .join("\n"); console.log(text); break; } const toolResults: Anthropic.ToolResultBlockParam[] = []; for (const block of response.content) { if (block.type !== "tool_use") continue; try { const result = await scalekit.actions.executeTool({ toolName: block.name, toolInput: block.input as Record, identifier: IDENTIFIER, }); toolResults.push({ type: "tool_result", tool_use_id: block.id, content: JSON.stringify(result.data), }); } catch (err) { toolResults.push({ type: "tool_result", tool_use_id: block.id, is_error: true, content: err instanceof Error ? err.message : String(err), }); } } messages.push({ role: "user", content: toolResults }); } // Response shapes for both clients are documented in the Node.js SDK reference and the Python SDK reference.

Virtual MCP servers for multi-tool, multi-tenant Bitly agents

A campaign agent rarely touches Bitly alone. It reads a content calendar, shortens the URL, posts to Slack, and writes results back to a CRM. Every one of those connectors brings its own tool surface, and the bloat compounds.

Virtual MCP Servers solve this by inverting the default. A standard MCP server exposes every tool it has. A Virtual MCP server exposes only the tools you explicitly declare, across whichever connections you choose, behind one static endpoint. This is the same pattern explored in building production-ready agent workflows with remote MCP servers.

Two objects, two phases

Setup happens once per agent role: define which connections and which tools the server exposes, and you get a static MCP server URL. Runtime happens before each run: confirm the user has authorized the required connections, mint a short-lived session token bound to that user, and hand the agent the URL plus the token.

The endpoint is static. The identity is not. One server definition serves every tenant, and no credential is shared between them.

Defining a scoped Bitly server

from scalekit.actions.types import McpConfigConnectionToolMapping config = actions.mcp.create_config( name="campaign-link-agent", description="Shortens campaign URLs and reports click performance", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="bitlymcp", tools=[ "bitlymcp_create_short_link_with_qr", "bitlymcp_link_clicks_summary", "bitlymcp_get_group_links_clicks_top", ], ), ], )

Minting a per-user endpoint

instance = actions.mcp.ensure_instance( config_name="campaign-link-agent", user_identifier="marketer@acme.com", ) auth_state = actions.mcp.get_instance_auth_state( instance_id=instance.instance.id, include_auth_links=True, ) for conn in auth_state.connections: if conn.connected_account_status != "ACTIVE": print(f"Needs authorization: {conn.connection_name} -> {conn.authentication_link}") mcp_url = instance.instance.url # hand this to the agent or IDE

Why this matters for Bitly specifically

Bitly's MCP server exposes write tools alongside read tools: creation, updates, and deletion sit next to analytics. An agent whose only job is weekly click reporting has no business holding bitlymcp_delete_short_link. Declaring three tools instead of fifty-three shrinks the blast radius to what you actually authorized, and it does so without deploying, hosting, or maintaining an MCP server of your own.

Observability: knowing which user's agent touched which link

Link management is an attribution problem before it is an automation problem. Bitly stamps created_by on every link and rolls creator identity into group reporting. An agent running on a shared token flattens all of that into one name.

Scalekit's connected account model preserves it. Each tool call resolves that user's credential server-side, so the link Bitly records was created by the marketer who asked for it, not by a service account. This is why agent tool observability matters as much as the tool calls themselves — you need to know not just that a tool ran, but whose authority it ran under.

What the audit trail gives you

Every downstream tool call is logged: who triggered it, which tool ran, and what came back. The Bitly MCP connector page documents 90 days of history, exportable to a SIEM. Auth logs cover the authorization side, so grant, token, and revocation events sit alongside the tool calls they authorize.

The question this answers

When someone asks why a production short link started redirecting somewhere unexpected, you need the answer in minutes, not a three-week investigation. Correlating the bitlymcp_update_short_link call with the connected account that authorized it and the prompt that triggered it is the difference between an incident report and a shrug.

The credential problem that exists on both paths

Both paths hand you a credential per user. Neither hands you a vault, rotation logic, or a revocation flow.

For a B2B product with 40 marketers across 8 customer organizations, that is 40 Bitly credentials to store encrypted and isolated per tenant, 40 to invalidate when someone leaves, and 40 to re-prompt when a user disconnects inside Bitly's settings.

Why Bitly makes this harder, not easier

Bitly's non-expiring tokens remove the one signal most teams accidentally rely on. There is no expiry-driven refresh cycle that would surface a dead credential on a predictable schedule. Revocation is silent until a call fails, and a failed call inside an agent run usually surfaces as a wrong answer rather than an exception.

The token type differs between the MCP path and the REST path. The credential management infrastructure required is identical. Scalekit's Bitly MCP connector handles the authorization flow, per-tenant token storage, and lifecycle for either path, so the MCP versus API decision does not change your auth architecture. The broader patterns around secure token management for AI agents at scale apply here regardless of which Bitly integration path you choose.

Which one to build against

If your agent creates links, generates QR codes, and reports on clicks and scans, build against the hosted MCP server. Bitly maintains the schemas, the tool surface is genuinely broad, and the static-token option means a scheduled job is not blocked on a browser flow the way it would be with Notion or Salesforce.

If your agent retags or archives in batches, subscribes to engagement webhooks, manages campaigns and channels, or exports data, use the REST API. Those surfaces do not exist in MCP, and looping single-item MCP calls to simulate a bulk endpoint burns a monthly allowance that is shared with your customers' human users.

Most production Bitly agents will use both. The credential layer underneath them is the same either way, and that is the part that needs production-grade infrastructure. When you are evaluating the difference between single-tenant and multi-tenant tool calling auth, the Bitly non-expiring token problem is exactly the kind of constraint that forces your hand.

Talk to other Bitly agent builders

Building on Bitly and hitting the five-connection IP ceiling, the bulk-update gap, or the non-expiring token problem? Join the Scalekit community on Slack and compare notes with engineers shipping the same patterns.

Need an answer today? Talk to an engineer and we will walk your architecture with you.

Start here: the Bitly MCP connector docs and the Bitly MCP connector overview.

No items found.
Agent
Auth Quickstart
On this page
Share this article
Agent
Auth Quickstart

Acquire enterprise customers with
zero upfront cost.

Every feature unlocked. No hidden fees.