Announcing CIMD support for MCP Client registration
Learn more

Ramp MCP vs Ramp API for AI Agents (2026)

Ramp now gives an AI agent two ways into a finance team's spend data: an official MCP server with one-click connectors for Claude, ChatGPT, Cursor, and Perplexity, and a REST Developer API with an OpenAPI schema and webhooks. They overlap on reading spend. They diverge on approvals, user administration, event delivery, and most sharply on whose identity an action runs under. This guide compares what each path lets an agent do, the grant each one puts you on, and how to run either across many customer businesses in production.

TL;DR

  • Ramp hosts an official MCP server at https://mcp.ramp.com/mcp, with sample data at https://demo-mcp.ramp.com/mcp. Ramp's Agent Tools reference counts 224 tools shared between the MCP server and the Ramp CLI, covering transactions, reimbursements, bills, cards and funds, approvals, treasury, procurement, and travel. It acts as the signed-in Ramp user, caps query responses at 100 rows, can't take receipt uploads, and has no event triggers. Ramp's own MCP guide says bill approvals aren't available through it.
  • The Ramp Developer API at https://api.ramp.com/developer/v1 is OAuth 2.0 only. Client Credentials covers your own business and issues 10-day tokens with no refresh token. Authorization Code covers third-party apps: access tokens last 1 hour by default, and refreshing never extends the refresh token's own expiry, so every grant eventually needs a fresh consent. Rate limits are 200 requests per 10-second rolling window, per source IP.
  • The two paths carry different identities. MCP calls run with the signed-in user's Ramp role. A third-party API grant can only be approved by an Admin or Business Owner, and Ramp's connection guide says API actions are scoped to the business owner's identity. On the API path, per-user restrictions are yours to enforce.
  • MCP is the faster path for a finance lead asking Claude or ChatGPT which card charges are still missing receipts. The API is the right path for headless agents that react to transactions.cleared webhooks, chase memos, provision cards and funds, or onboard and offboard employees.
  • Scalekit's ramp connector exposes 114 prebuilt tools over the Ramp API and runs the OAuth flow, token storage, and refresh through your own Ramp OAuth app, so the MCP vs API decision doesn't change your auth infrastructure.

The decision your Ramp agent forces first

Your agent needs to work inside your customers' Ramp accounts: find the card charges from last month that still have no receipt, nudge the cardholder in the transaction's comment thread, add a memo, freeze a card that just hit a blocked merchant, or set up a fund for a new hire. Ramp now hosts an official Model Context Protocol (MCP) server with one-click connectors for Claude, ChatGPT, Cursor, and Perplexity. Ramp also has a REST Developer API with an OpenAPI schema and webhooks. They overlap on reading spend. They diverge on approvals, user administration, event delivery, and, most sharply, on whose identity an action runs under. Here's how to pick.

What Ramp MCP and Ramp API actually are

Ramp MCP

Ramp MCP is a remote server at https://mcp.ramp.com/mcp. Clients authenticate with OAuth in the browser, and "the connection acts as the Ramp user who completes browser authorization," according to Ramp's connection guide. Businesses with more than one Ramp account can use an alias of the form https://mcp.ramp.com/<business-identifier>/mcp, and each business keeps its own OAuth session. Ramp's developer docs state plainly that "Ramp MCP is subject to change" and give no GA date.

Ramp actually ships three MCP servers. The account-scoped Ramp MCP is the one this article compares. The Ramp Data MCP at https://mcp.ramp.com/ramp-data/mcp serves Ramp Rate and AI Index benchmark data behind a partner API key. The Developer MCP at https://mcp.ramp.com/developer/mcp is unauthenticated and serves API docs through tools such as developer_docs_search and developer_docs_schema. For coding agents, Ramp recommends the Ramp CLI over MCP.

The Agent Tools reference lists 224 tools in its reviewed snapshot, shared by MCP and the CLI, with names such as ramp_approve_or_reject_transaction, ramp_lock_or_unlock_card, ramp_get_missing_items_by_user, ramp_reporting_query, and ramp_create_draft_bill. Before the hosted server, Ramp published a local server, ramp-public/ramp_mcp, that authenticated with Client Credentials and loaded API data into an in-memory SQLite database for the model to query. That repository was archived on July 17, 2026.

Ramp API

The Ramp Developer API is a REST API at https://api.ramp.com/developer/v1, with a sandbox at https://demo-api.ramp.com. It covers transactions, reimbursements, receipts, memos, bills and draft bills, vendors and vendor agreements, purchase orders, cards and virtual cards, funds and spend programs, users, departments, locations, entities, accounting fields and sync, statements, transfers, cashbacks, treasury, comments, audit logs, and webhooks. Every request carries Authorization: Bearer <access token>, issued through OAuth 2.0: Client Credentials for your own business, or Authorization Code with a refresh token for apps your customers connect. Apps are created by a Ramp Admin under Company > Developer, separately for sandbox and production.

Comparing them where it matters for agents

What your agent can actually do

Capability
Ramp MCP (mcp.ramp.com/mcp)
Ramp API (api.ramp.com/developer/v1)
Search and read card transactions
Yes, Searches transactions and reimbursements; responses capped at 100 rows
Yes, Scalekit ramp_transaction_list with filters for approval_status, sync_status, requires_memo, and more; ramp_transaction_get
Memos, splits, and accounting coding
Yes, Edits transaction details and applies coding
Yes, Scalekit ramp_memo_create, ramp_transaction_split, ramp_accounting_field_option_list
Approve or reject spend
Yes, ramp_approve_or_reject_transaction, ramp_approve_or_reject_reimbursement, ramp_approve_or_reject_request, approvers only
Limited: Scalekit ramp_approval_list reads approval chains; the connector has no approve or reject tool
Bills
Limited: lists bills and creates drafts with ramp_create_draft_bill; Ramp's MCP guide says bill approvals aren't available
Yes, Scalekit ramp_bill_create, ramp_bill_draft_create, ramp_bill_hold, ramp_bill_release
Lock or freeze cards
Yes, ramp_lock_or_unlock_card, ramp_activate_card
Yes, Scalekit ramp_card_suspend, ramp_card_unsuspend, ramp_card_update
Funds and spend programs
Yes, ramp_issue_from_spend_program, ramp_limit_increase, ramp_update_merchant_restrictions
Yes, Scalekit ramp_fund_create, ramp_fund_update, ramp_fund_suspend, ramp_spend_program_create
Receipts
Limited: finds missing receipts with ramp_get_missing_items_by_user; no uploads, and chat attachments are not forwarded
Limited: Scalekit ramp_receipt_list and ramp_receipt_get with OCR data; no upload tool in the connector
User administration
Org chart reads only
Yes, Scalekit ramp_user_invite_create, ramp_user_update, ramp_user_deactivate, ramp_user_reactivate
Purchase orders and vendors
Yes, Vendor search plus RFX tools such as ramp_create_rfx
Yes, Scalekit ramp_purchase_order_create, ramp_vendor_create, ramp_vendor_agreement_create
Aggregate spend analysis
Yes, ramp_reporting_query and analyst tools; company-wide analysis needs Admin or Business Owner
Limited: list endpoints only, 100 items per page; you aggregate
Treasury and agent payments
Yes, Treasury balances plus Agent Card and x402 tools such as ramp_get_agent_card_creds
Limited: Scalekit ramp_banking_account_list, ramp_business_balance_get, read only
Event notifications
No triggers
Yes, Webhooks through POST /developer/v1/webhooks, more than 40 event types; not a connector tool

Ramp is explicit about where each path fits. Its connection guide rates MCP as "Instant to 15 minutes" to set up and running "inside a conversational assistant," and the API as "Hours to days" for "deterministic code," with your team owning "OAuth, scopes, webhooks, retries, and the customer experience." The MCP server is built for a person asking questions and approving things in chat. The API is built for systems that run the close without anyone watching.

What the MCP path makes easier

Answering "how much did Engineering spend on software in Q3, by vendor" on the raw API is a pagination exercise. You resolve the department ID, page through GET /developer/v1/transactions 100 rows at a time with date filters, map merchants to vendors, and sum amounts yourself, all while each page lands in the model's context.

Ramp MCP moves that work server side. The original open-source server loaded API data into an in-memory SQLite database and let the model run SQL against it, precisely "to get around token and input size limitations." The hosted server still reports errors such as "ETL operation limit reached" and tells users to clear unused tables, which suggests the same load-then-query model behind ramp_reporting_query and the analyst tools. Approvals are the other shortcut: ramp_get_requests_to_review and ramp_approve_or_reject_request let an approver clear a queue in one conversation, a capability the Scalekit connector does not expose at all. Writing the schema is the hard part. Not the API call.

The tradeoff is that Ramp owns the surface. A query that returns more than 100 rows returns no data until the model narrows it, the tool set is "subject to change," and 224 tools is a large catalog to put in front of a model. An agent tuned against today's tool names changes behavior without a release on your side.

The auth path each one puts you on

On the MCP path, auth is OAuth in the browser, and the result is a user-level identity. Ramp says the AI tool "can only see data and take actions that the signed-in user can already perform in Ramp" and that "Ramp attributes MCP calls to the signed-in user." Admins decide who may connect under Company > Integrations > Ramp MCP > Manage access, and on Ramp Plus they can grant connection rights by role. Off-the-shelf clients work immediately. A custom client or gateway must first get its exact redirect URI allowlisted through Ramp's request form, and wildcard subdomains are not supported.

On the direct API path, the authorization guide defines two grants. Client Credentials is server-to-server for your own Ramp business: tokens normally last 10 days (864,000 seconds), there is no refresh token, and since July 16, 2026 omitting scope returns a token with no scopes at all. Authorization Code is for third-party apps: the user consents at https://app.ramp.com/v1/authorize, codes expire in 10 minutes, state is required, and PKCE with code_challenge became optional on June 22, 2026. Access tokens default to 1 hour and are configurable per app. A refresh does not return a new refresh token and does not extend the existing one, so when refresh_token_expires_in runs out the user must consent again.

Scopes are granular and bound at issuance: transactions:read, transactions:write, receipts:read, memos:write, cards:write, limits:write, users:write, bills:write, and around 50 more. cards:read_vault, which exposes full card numbers, requires an extra security review. Changing scopes means a new token.

Who can grant is the Ramp-specific catch. Only users with Developer API authorization permissions, "typically Admins and Business Owners," can approve a third-party app; anyone else gets "Business not authorized to use this application." Ramp's connection guide says API actions are scoped to the business owner's identity, while MCP and CLI actions run as the acting user. What the user can't do, the agent can't do: on MCP, Ramp enforces that for you. On the API, the grant reflects the scopes an admin approved for the whole business, and the line between what a cardholder may see and what a controller may see is yours to draw.

Both paths still need per-user isolation

In a multi-tenant B2B agent, MCP gives you a credential per user and the direct API gives you a credential per user. In neither case does the path itself solve storage, rotation, or revocation; those are infrastructure problems regardless of which path you choose.

Ramp shifts where the boundary sits. On the API path, the natural unit is the Ramp business, connected once by an admin, and that one connected account can read every transaction its scopes allow. Your app has to bind each connected account to exactly one customer, and then filter by user_id, department_id, or entity_id when an ordinary employee asks a question, because Ramp will answer for the whole business. On the MCP path, each employee connects separately, which matches Ramp's permission model but multiplies the sessions you track.

Recommended reading: access control for multi-tenant AI agents

What you own in production

On the MCP path, Ramp hosts the server, owns the tool schemas, and enforces the user's role. You own each user's OAuth session inside a client you may not control, and the reconnect steps when auth goes stale. You also own the gaps. MCP tools only run inside a conversation someone starts, so nothing happens when a charge clears. Receipts can't be uploaded through it. Any result over 100 rows needs a narrower query, and heavy analysis can hit the "ETL operation limit" until running loads finish.

On the API path, you own schemas, pagination, retries, and the token lifecycle. Ramp's rate limits are 200 requests per 10-second rolling window "per source IP address," and requests that run longer than 60 seconds fail with 504. Ramp recommends exponential backoff on 429 and 504, and pages hold at most 100 items, with a start cursor taken from page.next.

The per-IP limit is the one that bites multi-tenant agents. If every customer's agent runs from the same egress IP, one tenant's backfill spends the budget for all of them. Incremental sync has its own traps: synced_after on transactions only matches accounting syncs, so it misses new and late-posted charges, and bills only filter on from_created_at, so status changes need webhooks or full re-pulls.

Webhooks close that gap, with rules. A subscription created through POST /developer/v1/webhooks starts in pending_verification until your endpoint echoes a challenge, and your token needs the read scope for each event type, such as transactions:read for transactions.cleared. Each delivery is signed with HMAC-SHA256 over the raw body in X-Ramp-Signature, keyed by the secret returned at creation. Payloads are thin, carrying id, type, created_at, business_id, and object, so the agent fetches the resource. Your endpoint has 10 seconds to return a 2xx; Ramp makes up to 10 attempts with jittered backoff, events can arrive out of order, and Ramp warns that events missed during extended downtime may not be recoverable.

When to use MCP, when to use the API

Use Ramp MCP when:

  • A controller in Claude or ChatGPT asks "which of my team's card charges from last week are missing receipts" and approves or rejects the stragglers with ramp_approve_or_reject_transaction in the same conversation.
  • A finance lead wants aggregate answers, such as SaaS spend by department for the quarter, and the server-side query tools beat paging through raw transactions.
  • An employee needs self-service: why a card was declined, which policy applies, or locking their own card while they look for it.
  • You are prototyping a Ramp assistant for your own finance team, with each person connected under their own role and a human in every loop.

Use the Ramp API when:

  • The agent runs headless: a transactions.cleared webhook triggers it to read the charge with ramp_transaction_get and post a receipt request with ramp_comment_create.
  • The workflow is a deterministic pipeline: issuing a fund from a spend program for each new hire, or deactivating a departing employee with ramp_user_deactivate when your HRIS fires an offboarding event.
  • You serve many customer businesses and need connected accounts per business, webhooks per business, and a rate budget you can plan against.
  • The task needs scopes the MCP server doesn't expose to your agent, such as user administration, purchase order creation, or bill holds before payment.

The credential problem that exists on both paths

Both paths hand you a Ramp credential per user. Neither hands you a vault. A multi-tenant agent serving 80 customer businesses holds 80 admin-approved grants on the API path, each with an access token that expires every hour and a refresh token whose expiry never moves. Behind them sits your own Ramp OAuth app, with a client secret per environment that Ramp's support team treats as an incident if leaked. Every token must be stored encrypted, kept out of logs and LLM context, refreshed before it expires, revoked through POST /developer/v1/token/revoke when a customer disconnects, and re-consented when the refresh window closes.

The MCP path changes the count, not the problem. Per-employee OAuth sessions match Ramp's permission model better, but 80 businesses with 15 finance users each is 1,200 sessions living in chat clients. The tempting shortcut on either path is one Client Credentials token per customer, created by their admin. That gives you 10-day tokens with no user identity at all, every action attributed to the app, and an admin-level credential sitting in your database. Fewer secrets, larger blast radius per secret.

Scalekit's ramp connector handles the Ramp OAuth flow, token storage, and refresh for every connected account, so the MCP vs API decision doesn't change your auth infrastructure. Your agent passes an identifier; credentials never touch the agent runtime or the LLM context.

Recommended reading: how to handle token refresh for AI agents

How to connect a Ramp agent with Scalekit

Scalekit's ramp connector is an API-path connector built by Scalekit with 114 tools: 71 read, 41 write, and 2 destructive. It covers transactions, memos, splits, receipts, reimbursements, approvals, bills and draft bills, vendors, vendor agreements, purchase orders, cards and virtual cards, funds, spend programs, users, departments, locations, entities, accounting fields, statements, transfers, cashbacks, banking balances, comments, AI spend, and audit logs. It authenticates with OAuth 2.0: each customer's Ramp admin signs in once on Ramp's consent screen, and Scalekit stores and refreshes the tokens.

Prerequisites and the connection name

In the Scalekit dashboard, go to AgentKit > Connections, create a Ramp connection, and copy its redirect URI. The name you give the connection is the connection_name your code passes, and the string must match the dashboard exactly. This is the most common integration error.

Ramp connections require your own OAuth app. A Ramp Admin creates it under Company > Developer, enables the Authorization Code and Refresh Token grant types, adds the Scalekit redirect URI, and selects the scopes your tools need. For the examples below that means business:read, users:read, departments:read, transactions:read, transactions:write, and receipts:read; each tool's entry in the connector docs states the scope it needs. Paste the app's Client ID and Client Secret into the Ramp connection. Sandbox and production need separate Ramp apps. Copy SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, and SCALEKIT_CLIENT_SECRET from Developers > API Credentials into .env.

pip install scalekit-sdk-python python-dotenv langchain-openai

Authorize each customer once

get_or_create_connected_account returns the connected account for an identifier. If it is not ACTIVE, get_authorization_link returns a link that sends the user to Ramp's OAuth consent screen for your app. The person who clicks it must be an Admin or Business Owner of the Ramp business, or Ramp rejects the grant. Scalekit exchanges the code and stores the tokens; the agent never sees them. Here the identifier is your customer's ID, because the grant covers the whole Ramp business.

import os from dotenv import load_dotenv from scalekit import ScalekitClient load_dotenv() scalekit_client = ScalekitClient( env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], ) actions = scalekit_client.actions CONNECTION_NAME = "ramp" # must match the connection name in AgentKit > Connections identifier = "customer_acme" # your app's stable ID for this customer's Ramp business account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=identifier, ) if account.connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name=CONNECTION_NAME, identifier=identifier, ) print("A Ramp Admin or Business Owner should open:", link.link) input("Press Enter after approving access in Ramp...") account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=identifier, ) if account.connected_account.status != "ACTIVE": raise RuntimeError(f"Ramp is {account.connected_account.status}, not ACTIVE")

Retrieve the Ramp tools this account can call

This is not a catalog load. The agent asks for the tools this connected account is authorized to call, then narrows them to the six a receipt-review agent needs. Loading all 114 Ramp tools would put ramp_user_deactivate, ramp_fund_suspend, and ramp_bill_create in front of a model whose job is to find missing receipts. None of the six below changes anything in Ramp.

tools = actions.langchain.get_tools( identifier=identifier, connection_names=[CONNECTION_NAME], tool_names=[ "ramp_business_get", "ramp_department_list", "ramp_user_list", "ramp_transaction_list", "ramp_transaction_get", "ramp_receipt_list", ], page_size=100, ) tool_map = {t.name: t for t in tools} print(sorted(tool_map))

Run the agent loop

The loop runs until the model stops requesting tools. Every call executes with this customer's Ramp grant, so the system prompt pins the agent to one department; Ramp's API would otherwise answer for the whole business.

from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) messages = [ SystemMessage( "You review Ramp card spend for one department. Use ramp_business_get to " "confirm the business, ramp_department_list to resolve the department ID, " "and always pass that department_id to ramp_transaction_list. Use " "ramp_receipt_list with transaction_id to confirm a receipt is missing " "before reporting it. Dates are ISO 8601. Never guess IDs." ), HumanMessage( "List Engineering card transactions over $75 from 2026-09-01 to 2026-09-30 " "that have no receipt, grouped by cardholder, with merchant and amount." ), ] while True: response = llm.invoke(messages) messages.append(response) if not response.tool_calls: print(response.content) break for tc in response.tool_calls: tool = tool_map.get(tc["name"]) if tool: result = tool.invoke(tc["args"]) else: result = f"Unknown tool: {tc['name']}" messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))

Deterministic calls with execute_tool

For pipelines, skip the model. execute_tool runs one tool with a fixed input and returns data plus an execution_id you can log. This handler runs when your webhook endpoint receives a transactions.cleared event: it verifies the X-Ramp-Signature header, fetches the transaction, and if a charge of $75 or more has no receipt, posts a comment on the transaction that mentions the cardholder.

import base64 import hashlib import hmac import json RECEIPT_THRESHOLD_MINOR_UNITS = 7500 # $75.00; Ramp amount objects use minor units def verify_ramp_signature(raw_body: bytes, signature: str, secret: str) -> bool: """Ramp signs the raw request body with HMAC-SHA256 using the subscription secret.""" digest = hmac.new(secret.encode(), raw_body, hashlib.sha256).digest() received = signature.strip() if received.startswith("sha256="): received = received[len("sha256="):] return hmac.compare_digest(received, digest.hex()) or hmac.compare_digest( received, base64.b64encode(digest).decode() ) def handle_transaction_cleared(raw_body: bytes, signature: str, identifier: str) -> None: secret = os.environ["RAMP_WEBHOOK_SECRET"] # stored per customer subscription if not verify_ramp_signature(raw_body, signature, secret): raise PermissionError("Invalid X-Ramp-Signature") event = json.loads(raw_body) if event.get("type") != "transactions.cleared": return transaction_id = event["object"]["id"] txn = actions.execute_tool( tool_name="ramp_transaction_get", tool_input={"transaction_id": transaction_id}, connection_name=CONNECTION_NAME, identifier=identifier, ) data = txn.data print(txn.execution_id, transaction_id) entity_amount = data.get("entity_amount") or {} amount_minor = entity_amount.get("value") if amount_minor is None or abs(amount_minor) < RECEIPT_THRESHOLD_MINOR_UNITS: return if data.get("receipts"): return holder_id = (data.get("card_holder") or {}).get("user_id") merchant = data.get("merchant_name", "this merchant") comment = actions.execute_tool( tool_name="ramp_comment_create", tool_input={ "object_type": "transactions", "object_id": transaction_id, "message": ( f"This {merchant} charge needs a receipt under the $75 policy. " "Please upload it in Ramp within 3 business days." ), "mention_user_ids": [holder_id] if holder_id else [], }, connection_name=CONNECTION_NAME, identifier=identifier, ) print(comment.execution_id, comment.data)

The webhook subscription itself is not a connector tool. Create it once per customer business with POST /developer/v1/webhooks using that business's grant, which Scalekit can make on the connected account's behalf through proxied API calls, and store the returned secret per subscription. Because Ramp may retry and reorder deliveries, deduplicate on the event id before calling the handler. The Ramp MCP server has no equivalent for either step.

Two other writes show where the API path earns its place. ramp_card_suspend takes a card_id and blocks a physical card in one call, and ramp_user_deactivate takes a user_id and cuts off login and card spend for a departing employee. Neither belongs in a model's default tool set; both fit a pipeline with a human approval step in front of them.

Multi-tool, multi-tenant Ramp agents on a virtual MCP server

The ramp connector has 114 tools. At roughly 200 tokens each, that is about 22,800 tokens of tool definitions before the agent does any work, and Ramp's own MCP catalog of 224 tools is about twice that. A model choosing among ramp_transaction_list, ramp_reimbursement_list, ramp_receipt_list, ramp_memo_list, and ramp_approval_list picks the wrong list more often than a model holding only the one it needs. Overreach is the bigger risk: a receipt chaser that also holds ramp_fund_suspend and ramp_user_deactivate can freeze a team's spend on a bad inference. The fix is not better prompting. It is surface reduction. A Scalekit virtual MCP server declares exactly which tools an agent role can see, and session tokens keep each run bound to one customer's connected accounts. Docs: set up a virtual MCP server.

Create the server once per agent role

from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping vmcp = actions.mcp.create_config( name="ramp-receipt-chaser", description="Finds Ramp charges missing receipts, comments on them, and posts a Slack digest", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="ramp", tools=[ "ramp_transaction_list", "ramp_transaction_get", "ramp_receipt_list", "ramp_comment_create", ], ), McpConfigConnectionToolMapping( connection_name="slack", tools=["slack_send_message"], ), ], ) print(vmcp.config.id, vmcp.config.mcp_server_url)

Mint a session token per customer, per run

One server definition serves every customer. Before each run, check that the customer's accounts are active and mint a short-lived session token scoped to them. Any MCP client that speaks Streamable HTTP can connect; this example uses langchain-mcp-adapters (pip install langchain-mcp-adapters) and passes the token as a Bearer header.

import asyncio from datetime import timedelta from langchain_mcp_adapters.client import MultiServerMCPClient config_id = vmcp.config.id mcp_server_url = vmcp.config.mcp_server_url customer_id = "customer_acme" accounts = actions.mcp.list_mcp_connected_accounts( config_id=config_id, identifier=customer_id, include_auth_link=True, ) not_ready = [ a for a in accounts.connected_accounts if str(a.connected_account_status).upper() != "ACTIVE" ] for a in not_ready: print(f"{a.connection_name} is {a.connected_account_status}: {a.authentication_link}") if not_ready: raise RuntimeError("Connect the accounts above before starting this run") session_token = actions.mcp.create_session_token( mcp_config_id=config_id, identifier=customer_id, expiry=timedelta(minutes=30), ).token async def load_tools(): client = MultiServerMCPClient( { "scalekit": { "transport": "streamable_http", "url": mcp_server_url, "headers": {"Authorization": f"Bearer {session_token}"}, } } ) return await client.get_tools() mcp_tools = asyncio.run(load_tools()) print([t.name for t in mcp_tools])

The agent sees five tools instead of 114. The endpoint is static; the identity is per customer. Unlike Ramp's own MCP server, this endpoint can include tools Ramp MCP doesn't offer, such as ramp_user_deactivate, for the one offboarding role that should hold it, and leave them out of every other role.

Observability: every Ramp action tied to a customer

Every execute_tool call returns an execution_id, and every execution is attributed to a connected account, which maps to one of your customers. When a controller asks which agent run commented on 40 transactions overnight or suspended a card on Saturday, the answer is a lookup, not an investigation. Ramp keeps its own record too: ramp_audit_log_event_list filters Ramp's audit log by event_actor_types, agent_names, and object_id, and your execution log shows which run and which prompt led there.

Attribution is also where the paths differ. MCP calls show up under the employee who connected. API calls run under the business-level grant your customer's admin approved, so your execution log is the only place that records which of your users asked for the action. A shared credential across customers breaks even that: one token reaching several Ramp businesses means every action is attributed to the same grant, and a leak exposes all of them. Shared credentials are a single-user solution. They do not survive a second user.

Recommended reading: audit trails for agent auth

Which one to build against

If your agent is a finance team's assistant in Claude, ChatGPT, or Cursor, answering spend questions, clearing approval queues, and locking cards with a person watching, Ramp's official MCP server is a good fit: it is hosted, it enforces each user's Ramp role, and its server-side query tools handle aggregation the API leaves to you. If your agent runs unattended, reacts to transactions.cleared and bills.updated webhooks, chases memos and receipts across many customer businesses, or provisions and offboards users and funds, build against the Ramp API, where you choose the scopes and the operations. Either way, you hold an OAuth grant per Ramp business or per employee, with hourly access tokens and refresh tokens that eventually force re-consent, and that's what needs production-grade infrastructure.

FAQs

Does Ramp have an official MCP server?

Yes. Ramp hosts it at https://mcp.ramp.com/mcp, with sample data at https://demo-mcp.ramp.com/mcp. Clients authenticate with OAuth in the browser and the connection acts as the Ramp user who completes authorization. Ramp's Agent Tools reference counts 224 tools shared between the MCP server and the Ramp CLI, covering transactions, reimbursements, bills, cards, funds, approvals, treasury, procurement, and travel. Ramp says the server is subject to change.

What can an agent not do through Ramp MCP?

Query responses are capped at 100 rows, receipts cannot be uploaded and chat attachments are not forwarded, user administration is read-only, there are no event triggers, and Ramp's own MCP guide says bill approvals are not available through it. Heavy analysis can also hit an ETL operation limit until running loads finish.

How do Ramp API tokens expire?

Client Credentials tokens for your own business normally last 10 days with no refresh token, and since July 16, 2026 omitting scope returns a token with no scopes at all. Authorization Code access tokens default to 1 hour. A refresh does not return a new refresh token and does not extend the existing one, so when refresh_token_expires_in runs out the user must consent again.

Who can authorize a third-party app on Ramp?

Only users with Developer API authorization permissions, typically Admins and Business Owners. Anyone else sees Business not authorized to use this application. Ramp's connection guide says API actions are scoped to the business owner's identity, while MCP and CLI actions run as the acting user, so on the API path per-employee restrictions are yours to enforce by filtering on user, department, or entity.

When should an agent use the Ramp API instead of MCP?

When it runs headless from a transactions.cleared or bills.updated webhook, when it provisions funds and cards or deactivates departing employees as a deterministic pipeline, when it serves many customer businesses with a connected account and webhooks per business, or when it needs scopes the MCP server does not expose to your agent, such as user administration, purchase order creation, or bill holds.

How does Scalekit connect an AI agent to Ramp?

The ramp connector exposes 114 tools over the Ramp Developer API and runs the OAuth flow, token storage, and refresh through your own Ramp OAuth app. An Admin or Business Owner at each customer consents once, Scalekit stores and refreshes the tokens, a virtual MCP server narrows the tools per agent role, and every execution returns an execution_id attributed to that customer's connected account.

Build your Ramp agent with Scalekit

Browse the Scalekit Ramp connector and the Ramp connector docs with the full tool list. See Scalekit pricing, or talk to us.

No items found.
On this page
Share this article
Scalekit
Per-business grants

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.