> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corsair.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Ramp plugin

> Corporate cards and spend management with Ramp - read card transactions, bills, reimbursements, virtual cards, merchants, users and departments, split a transaction into line items, edit a bill and attach memos.

Use **Ramp** through Corsair: one client, typed API calls.

Create a developer app in Ramp under **Company → Developer**, turn on the grant types you need, add the scopes under **Configure allowed scopes**, and register your redirect URI (Ramp only accepts an exact match). Then pick one of three ways to connect:

* **`oauth_2` with 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_2` with `clientCredentials: 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 `401` for a bad token, but the live API answers **404** with `error_v2.error_code: 'DEVELOPER_7002'`. The plugin reads that code, so `RampAPIError.isAuthError` is true for it and `isNotFound` is false.
* **Every route is scoped, twice.** The app must allow a scope before a token can carry it: an app without it answers `403` `DEVELOPER_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 gets `403`. `business.get` needs `business:read`, not `transactions:read`.
* **Splitting needs an accounting connection.** `transactions.update` answers `400` `DEVELOPER_7020` until the business connects an accounting provider in Ramp.
* **Paging is cursor based.** `start` is 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 returns `next_cursor` (read out of `page.next` for you) and `next_page_url`. `page_size` must 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.list` models the pair as `sort: { by, direction }` and sends exactly one.
* **Splitting a transaction must balance.** `transactions.update` replaces the line items, and their `amount` values 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-After` and 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.

**What you get:**

* 16 typed API operations

## Setup

**1. Install**

```bash npm theme={null}
npm install corsair @corsair-dev/ramp better-sqlite3
npm install --save-dev @types/better-sqlite3
```

```bash yarn theme={null}
yarn add corsair @corsair-dev/ramp better-sqlite3
yarn add --dev @types/better-sqlite3
```

```bash pnpm theme={null}
pnpm install corsair @corsair-dev/ramp better-sqlite3
pnpm install --save-dev @types/better-sqlite3
```

```bash bun theme={null}
bun add corsair @corsair-dev/ramp better-sqlite3
bun add --dev @types/better-sqlite3
```

**2. Add the plugin**

```ts corsair.ts theme={null}
import Database from 'better-sqlite3';
import { createCorsair } from 'corsair';
import { ramp } from '@corsair-dev/ramp';

export const corsair = createCorsair({
	plugins: [
		ramp({
			authType: 'oauth_2',
		}),
	],
	database: new Database('corsair.db'),
	kek: process.env.CORSAIR_KEK!,
	multiTenancy: true,
	hub: {
		projectApiKey: process.env.CORSAIR_API_KEY!,
		signingSecret: process.env.CORSAIR_SIGNING_SECRET!,
	},
});
```

With `multiTenancy: true`, scope calls with `corsair.withTenant(id)`. See [Quick start](https://docs.corsair.dev/quick-start.md) for KEK + Hub keys, and [Multi-tenancy](https://docs.corsair.dev/concepts/multi-tenancy.md) for account isolation.

**3. Choose authentication**

> [tab] OAuth 2.0 (Recommended)
>
> Open [hub.corsair.dev](https://hub.corsair.dev/dashboard), navigate to your project, and enter the **client ID** and **client secret** from your Ramp OAuth app.
>
> ```ts theme={null}
> ramp({
> 	authType: 'oauth_2',
> })
> ```
>
> More: [OAuth 2.0](https://docs.corsair.dev/concepts/oauth)

> [tab] API Key
>
> No setup required yet. When you make your first request as a tenant, Corsair prompts for the API key.
>
> ```ts theme={null}
> ramp()
> ```
>
> More: [API Key](https://docs.corsair.dev/concepts/api-key)

**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](https://docs.corsair.dev/management/connect).

```ts theme={null}
const { connectUrl } = await corsair.manage.connect.createLink({
	plugin: 'ramp',
	tenantId: 'acme',
});
// redirect the user's browser to connectUrl
```

## Example API calls

**List this month's card transactions**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.ramp.api.transactions.list({
	from_date: '2026-05-01T00:00:00Z',
	to_date: '2026-06-01T00:00:00Z',
	sort: { by: 'date', direction: 'desc' },
	page_size: 50,
});
```

**Attach a memo to a transaction**

```ts theme={null}
const tenant = corsair.withTenant('acme');
await tenant.ramp.api.memos.create({ transaction_id: 'fd14cd6a-846e-4994-9315-5a59e6bb465f', memo: 'Q2 team lunch' });
```

See the full list on the [API](https://docs.corsair.dev/plugins/ramp/api.md) page.

## What's next

> [card] [API reference](https://docs.corsair.dev/plugins/ramp/api.md)
>
> Every `ramp.api.*` operation with input and output types.

> [card] [Connect / OAuth](https://docs.corsair.dev/management/connect)
>
> createLink, Hub delivery, and tenant connect flows.

> [card] [Use with agents](https://docs.corsair.dev/mcp-adapters/mcp-adapters)
>
> Expose this plugin's operations as MCP tools.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.