oauth_2with Authorization code (recommended). Each business approves your app; Ramp returns an access token that lasts one hour and a refresh token. The plugin refreshes the access token before it expires (HTTP Basic client auth at the token URL). Ramp does not rotate refresh tokens for third-party apps, so the stored one stays.oauth_2withclientCredentials: true(single business). For your own company only: with no refresh token stored, the plugin mints a Client Credentials token (valid 10 days) from the app’s client ID and secret and mints a new one when it is about to expire. That token reaches the business that owns the app, and the client ID and secret are shared by every tenant, so it is off by default; without it, a tenant with no refresh token is asked to connect. The app needs the Client credentials grant type.api_key. Paste an access token. Ramp expires it (10 days for Client Credentials), and then every call fails until a new one is pasted.
scopes defaults to every scope the operations use (transactions:read, transactions:write, merchants:read, cards:read, bills:read, bills:write, reimbursements:read, memos:read, memos:write, departments:read, departments:write, users:read, business:read); pass fewer to narrow it. Set environment: 'sandbox' for a sandbox app: tokens only work against the host that issued them (demo-api.ramp.com for sandbox, api.ramp.com for production). Things to know before you call:
- A rejected token looks like a missing record. Ramp documents
401for a bad token, but the live API answers 404 witherror_v2.error_code: 'DEVELOPER_7002'. The plugin reads that code, soRampAPIError.isAuthErroris true for it andisNotFoundis false. - Every route is scoped, twice. The app must allow a scope before a token can carry it: an app without it answers
403DEVELOPER_7100(“These scopes are not allowed for this client”) on every call. A token without the scope, or a business without the plan feature, also gets403.business.getneedsbusiness:read, nottransactions:read. - Splitting needs an accounting connection.
transactions.updateanswers400DEVELOPER_7020until the business connects an accounting provider in Ramp. - Paging is cursor based.
startis the id of the last record on the page, not an offset, and it is only meaningful for the filters that produced it. Each list returnsnext_cursor(read out ofpage.nextfor you) andnext_page_url.page_sizemust be 2 to 100 and defaults to 20. - Only one ordering parameter per call. Ramp takes four separate sort booleans and rejects more than one, so
transactions.listmodels the pair assort: { by, direction }and sends exactly one. - Splitting a transaction must balance.
transactions.updatereplaces the line items, and theiramountvalues are in minor units that must sum to the transaction total. Sending an empty array removes an existing split. - Rate limits. 200 requests per 10 seconds per source IP, then a 60 second block. The plugin honours
Retry-Afterand otherwise waits the documented 60 seconds before retrying, up to three times. - Writes are not replayed on a 5xx. Ramp says to check whether a timed-out write landed before retrying it, so only reads are retried.
- Webhooks are not received. Ramp can deliver them, but this plugin does not verify or route deliveries.
- 16 typed API operations
Setup
1
Install
2
Add the plugin
corsair.ts
multiTenancy: true, scope calls with corsair.withTenant(id). See Quick start for KEK + Hub keys, and Multi-tenancy for account isolation.3
Choose authentication
- OAuth 2.0 (Recommended)
- API Key
Open hub.corsair.dev, navigate to your project, and enter the client ID and client secret from your Ramp OAuth app.More: OAuth 2.0
4
Connect a tenant
Mint a connect link and send the tenant to it. Hub hosts the page and delivers the result to your app — see Connect / OAuth.
Example API calls
List this month’s card transactionsWhat’s next
API reference
Every
ramp.api.* operation with input and output types.Connect / OAuth
createLink, Hub delivery, and tenant connect flows.
Use with agents
Expose this plugin’s operations as MCP tools.