diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 6161069bef..31d3c50806 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -1643,6 +1643,17 @@ export function RB2BIcon(props: SVGProps) { ) } +export function RampIcon(props: SVGProps) { + return ( + + + + ) +} + export function RedditIcon(props: SVGProps) { return ( = { qdrant: QdrantIcon, quiver: QuiverIcon, railway: RailwayIcon, + ramp: RampIcon, rb2b: RB2BIcon, rds: RDSIcon, reddit: RedditIcon, diff --git a/apps/docs/content/docs/en/integrations/meta.json b/apps/docs/content/docs/en/integrations/meta.json index d96c79dba7..414e1ad735 100644 --- a/apps/docs/content/docs/en/integrations/meta.json +++ b/apps/docs/content/docs/en/integrations/meta.json @@ -158,6 +158,7 @@ "qdrant", "quiver", "railway", + "ramp", "rb2b", "rds", "reddit", diff --git a/apps/docs/content/docs/en/integrations/ramp.mdx b/apps/docs/content/docs/en/integrations/ramp.mdx new file mode 100644 index 0000000000..5daec69b38 --- /dev/null +++ b/apps/docs/content/docs/en/integrations/ramp.mdx @@ -0,0 +1,649 @@ +--- +title: Ramp +description: Manage spend, transactions, reimbursements, bills, and receipts in Ramp +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Ramp](https://ramp.com/) is the finance operations platform that helps businesses manage corporate cards, expenses, reimbursements, and bill payments in one place. Ramp combines spend controls, automated receipt matching, and accounting integrations so finance teams can close their books faster and keep spend under control. + +With Ramp in Sim, you can: + +- **Track card spend**: List and inspect card transactions with filters for user, card, department, merchant, amount, state, and date range +- **Look up people and cards**: Query users, corporate cards, spend limits, and spend programs across the business +- **Monitor reimbursements**: List employee reimbursements and drill into individual requests +- **Stay on top of payables**: List bills, check statuses and due dates, and review vendor records with year-to-date spend +- **Upload receipts**: Send receipt images or PDFs straight to Ramp, attached to a specific transaction or auto-matched by Ramp +- **Check business health**: Pull business details, entities, and real-time balance and credit limit information + +These capabilities let your Sim agents automate finance operations end to end — from chasing missing receipts and posting daily spend digests to syncing transactions into tables and flagging bills before they're due. Connect a Ramp account once and every operation runs through the same secure OAuth credential. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate Ramp into your workflow to automate corporate spend operations. List and inspect card transactions, users, cards, spend limits, reimbursements, bills, departments, and vendors, and upload receipts directly to transactions. + + + +## Actions + +### `ramp_list_transactions` + +List card transactions in Ramp with optional filters + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `userId` | string | No | Filter transactions by user ID | +| `cardId` | string | No | Filter transactions by card ID | +| `departmentId` | string | No | Filter transactions by department ID | +| `merchantId` | string | No | Filter transactions by merchant ID | +| `state` | string | No | Filter by transaction state: ALL, CLEARED, COMPLETION, DECLINED, ERROR, PENDING, or PENDING_INITIATION. Declined transactions are only included when set to ALL or DECLINED. | +| `minAmount` | number | No | Only include transactions larger than this U.S. dollar amount | +| `maxAmount` | number | No | Only include transactions smaller than this U.S. dollar amount | +| `fromDate` | string | No | Only include transactions that occurred after this ISO 8601 timestamp | +| `toDate` | string | No | Only include transactions that occurred before this ISO 8601 timestamp | +| `pageSize` | number | No | Number of results per page \(between 2 and 100, default 20\) | +| `start` | string | No | Pagination cursor: the ID of the last entity from the previous page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `transactions` | array | List of Ramp card transactions | +| ↳ `id` | string | Unique identifier for the transaction | +| ↳ `amount` | number | Settled amount in U.S. dollars | +| ↳ `currency_code` | string | ISO 4217 currency code | +| ↳ `merchant_name` | string | Name of the merchant | +| ↳ `memo` | string | Memo attached to the transaction | +| ↳ `state` | string | Transaction state \(e.g. CLEARED, PENDING\) | +| ↳ `user_transaction_time` | string | When the transaction occurred | +| ↳ `card_id` | string | ID of the card used | +| ↳ `card_holder` | object | Cardholder details \(user and department\) | +| ↳ `receipts` | array | IDs of receipts attached to the transaction | +| `nextStart` | string | Cursor for the next page of results \(null when there are no more pages\) | + +### `ramp_get_transaction` + +Retrieve a single Ramp card transaction by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `transactionId` | string | Yes | ID of the transaction to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `transaction` | object | The requested Ramp transaction | +| ↳ `id` | string | Unique identifier for the transaction | +| ↳ `amount` | number | Settled amount in U.S. dollars | +| ↳ `currency_code` | string | ISO 4217 currency code | +| ↳ `merchant_name` | string | Name of the merchant | +| ↳ `memo` | string | Memo attached to the transaction | +| ↳ `state` | string | Transaction state \(e.g. CLEARED, PENDING\) | +| ↳ `user_transaction_time` | string | When the transaction occurred | +| ↳ `card_id` | string | ID of the card used | +| ↳ `card_holder` | object | Cardholder details \(user and department\) | +| ↳ `receipts` | array | IDs of receipts attached to the transaction | + +### `ramp_list_users` + +List users in the Ramp business with optional filters + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `email` | string | No | Filter users by email address | +| `departmentId` | string | No | Filter users by department ID | +| `pageSize` | number | No | Number of results per page \(between 2 and 100, default 20\) | +| `start` | string | No | Pagination cursor: the ID of the last entity from the previous page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `users` | array | List of users in the Ramp business | +| ↳ `id` | string | Unique identifier for the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `email` | string | Email address of the user | +| ↳ `role` | string | Role of the user in the business | +| ↳ `status` | string | Status of the user \(e.g. ACTIVE\) | +| ↳ `department_id` | string | ID of the user department | +| ↳ `manager_id` | string | ID of the user manager | +| `nextStart` | string | Cursor for the next page of results \(null when there are no more pages\) | + +### `ramp_get_user` + +Retrieve a single Ramp user by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `userId` | string | Yes | ID of the user to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `user` | object | The requested Ramp user | +| ↳ `id` | string | Unique identifier for the user | +| ↳ `first_name` | string | First name of the user | +| ↳ `last_name` | string | Last name of the user | +| ↳ `email` | string | Email address of the user | +| ↳ `role` | string | Role of the user in the business | +| ↳ `status` | string | Status of the user \(e.g. ACTIVE\) | +| ↳ `department_id` | string | ID of the user department | +| ↳ `manager_id` | string | ID of the user manager | + +### `ramp_list_cards` + +List corporate cards in Ramp with optional filters + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `userId` | string | No | Filter cards by cardholder user ID | +| `displayName` | string | No | Filter cards by display name | +| `pageSize` | number | No | Number of results per page \(between 2 and 100, default 20\) | +| `start` | string | No | Pagination cursor: the ID of the last entity from the previous page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `cards` | array | List of Ramp corporate cards | +| ↳ `id` | string | Unique identifier for the card | +| ↳ `display_name` | string | Display name of the card | +| ↳ `last_four` | string | Last four digits of the card number | +| ↳ `cardholder_id` | string | User ID of the cardholder | +| ↳ `cardholder_name` | string | Full name of the cardholder | +| ↳ `is_physical` | boolean | Whether the card is physical | +| ↳ `state` | string | State of the card \(e.g. ACTIVE\) | +| ↳ `expiration` | string | Expiration date of the card | +| `nextStart` | string | Cursor for the next page of results \(null when there are no more pages\) | + +### `ramp_get_card` + +Retrieve a single Ramp corporate card by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `cardId` | string | Yes | ID of the card to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `card` | object | The requested Ramp card | +| ↳ `id` | string | Unique identifier for the card | +| ↳ `display_name` | string | Display name of the card | +| ↳ `last_four` | string | Last four digits of the card number | +| ↳ `cardholder_id` | string | User ID of the cardholder | +| ↳ `cardholder_name` | string | Full name of the cardholder | +| ↳ `is_physical` | boolean | Whether the card is physical | +| ↳ `state` | string | State of the card \(e.g. ACTIVE\) | +| ↳ `expiration` | string | Expiration date of the card | + +### `ramp_list_limits` + +List spend limits in Ramp with optional filters + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `userId` | string | No | Filter limits by user ID | +| `cardId` | string | No | Filter limits by card ID | +| `pageSize` | number | No | Number of results per page \(between 2 and 100, default 20\) | +| `start` | string | No | Pagination cursor: the ID of the last entity from the previous page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `limits` | array | List of Ramp spend limits | +| ↳ `id` | string | Unique identifier for the spend limit | +| ↳ `display_name` | string | Display name of the spend limit | +| ↳ `state` | string | State of the spend limit \(e.g. ACTIVE\) | +| ↳ `balance` | object | Balance of the spend limit \(cleared, pending, and total canonical amounts in the smallest currency denomination\) | +| ↳ `users` | array | Users the spend limit applies to | +| ↳ `cards` | array | Cards attached to the spend limit | +| ↳ `spend_program_id` | string | ID of the associated spend program | +| `nextStart` | string | Cursor for the next page of results \(null when there are no more pages\) | + +### `ramp_get_limit` + +Retrieve a single Ramp spend limit by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `limitId` | string | Yes | ID of the spend limit to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `limit` | object | The requested Ramp spend limit | +| ↳ `id` | string | Unique identifier for the spend limit | +| ↳ `display_name` | string | Display name of the spend limit | +| ↳ `state` | string | State of the spend limit \(e.g. ACTIVE\) | +| ↳ `balance` | object | Balance of the spend limit \(cleared, pending, and total canonical amounts in the smallest currency denomination\) | +| ↳ `users` | array | Users the spend limit applies to | +| ↳ `cards` | array | Cards attached to the spend limit | +| ↳ `spend_program_id` | string | ID of the associated spend program | + +### `ramp_list_reimbursements` + +List reimbursements in Ramp with optional filters + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `userId` | string | No | Filter reimbursements by user ID | +| `fromDate` | string | No | Only include reimbursements created after this ISO 8601 timestamp | +| `toDate` | string | No | Only include reimbursements created before this ISO 8601 timestamp | +| `pageSize` | number | No | Number of results per page \(between 2 and 100, default 20\) | +| `start` | string | No | Pagination cursor: the ID of the last entity from the previous page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `reimbursements` | array | List of Ramp reimbursements | +| ↳ `id` | string | Unique identifier for the reimbursement | +| ↳ `amount` | number | Reimbursement amount | +| ↳ `currency` | string | ISO 4217 currency code | +| ↳ `merchant` | string | Merchant the expense was made at | +| ↳ `memo` | string | Memo attached to the reimbursement | +| ↳ `state` | string | State of the reimbursement \(e.g. APPROVED\) | +| ↳ `type` | string | Type of reimbursement | +| ↳ `user_id` | string | ID of the user being reimbursed | +| ↳ `user_full_name` | string | Full name of the user | +| ↳ `created_at` | string | When the reimbursement was created | +| ↳ `transaction_date` | string | Date of the underlying expense | +| `nextStart` | string | Cursor for the next page of results \(null when there are no more pages\) | + +### `ramp_get_reimbursement` + +Retrieve a single Ramp reimbursement by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `reimbursementId` | string | Yes | ID of the reimbursement to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `reimbursement` | object | The requested Ramp reimbursement | +| ↳ `id` | string | Unique identifier for the reimbursement | +| ↳ `amount` | number | Reimbursement amount | +| ↳ `currency` | string | ISO 4217 currency code | +| ↳ `merchant` | string | Merchant the expense was made at | +| ↳ `memo` | string | Memo attached to the reimbursement | +| ↳ `state` | string | State of the reimbursement \(e.g. APPROVED\) | +| ↳ `type` | string | Type of reimbursement | +| ↳ `user_id` | string | ID of the user being reimbursed | +| ↳ `user_full_name` | string | Full name of the user | +| ↳ `created_at` | string | When the reimbursement was created | +| ↳ `transaction_date` | string | Date of the underlying expense | +| ↳ `receipts` | array | IDs of receipts attached to the reimbursement | + +### `ramp_list_bills` + +List bills in Ramp with optional filters + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `vendorId` | string | No | Filter bills by vendor ID | +| `pageSize` | number | No | Number of results per page \(between 2 and 100, default 20\) | +| `start` | string | No | Pagination cursor: the ID of the last entity from the previous page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `bills` | array | List of Ramp bills | +| ↳ `id` | string | Unique identifier for the bill | +| ↳ `invoice_number` | string | Invoice number of the bill | +| ↳ `amount` | object | Canonical bill amount \(integer amount in the smallest currency denomination plus currency code\) | +| ↳ `status` | string | Status of the bill \(e.g. OPEN, PAID\) | +| ↳ `due_at` | string | When the bill is due | +| ↳ `issued_at` | string | When the bill was issued | +| ↳ `vendor` | object | Vendor the bill is payable to | +| ↳ `memo` | string | Memo attached to the bill | +| `nextStart` | string | Cursor for the next page of results \(null when there are no more pages\) | + +### `ramp_get_bill` + +Retrieve a single Ramp bill by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `billId` | string | Yes | ID of the bill to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `bill` | object | The requested Ramp bill | +| ↳ `id` | string | Unique identifier for the bill | +| ↳ `invoice_number` | string | Invoice number of the bill | +| ↳ `amount` | object | Canonical bill amount \(integer amount in the smallest currency denomination plus currency code\) | +| ↳ `status` | string | Status of the bill \(e.g. OPEN, PAID\) | +| ↳ `due_at` | string | When the bill is due | +| ↳ `issued_at` | string | When the bill was issued | +| ↳ `vendor` | object | Vendor the bill is payable to | +| ↳ `memo` | string | Memo attached to the bill | +| ↳ `line_items` | array | Line items on the bill | + +### `ramp_list_departments` + +List departments in the Ramp business + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `pageSize` | number | No | Number of results per page \(between 2 and 100, default 20\) | +| `start` | string | No | Pagination cursor: the ID of the last entity from the previous page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `departments` | array | List of departments in the Ramp business | +| ↳ `id` | string | Unique identifier for the department | +| ↳ `name` | string | Name of the department | +| `nextStart` | string | Cursor for the next page of results \(null when there are no more pages\) | + +### `ramp_get_department` + +Retrieve a single Ramp department by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `departmentId` | string | Yes | ID of the department to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `department` | object | The requested Ramp department | +| ↳ `id` | string | Unique identifier for the department | +| ↳ `name` | string | Name of the department | + +### `ramp_create_department` + +Create a new department in the Ramp business + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `departmentName` | string | Yes | Name of the department to create | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `department` | object | The created Ramp department | +| ↳ `id` | string | Unique identifier for the department | +| ↳ `name` | string | Name of the department | + +### `ramp_list_vendors` + +List vendors in Ramp with optional filters + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `vendorName` | string | No | Filter vendors by name | +| `pageSize` | number | No | Number of results per page \(between 2 and 100, default 20\) | +| `start` | string | No | Pagination cursor: the ID of the last entity from the previous page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `vendors` | array | List of Ramp vendors | +| ↳ `id` | string | Unique identifier for the vendor | +| ↳ `name` | string | Name of the vendor | +| ↳ `state` | string | State of the vendor record | +| ↳ `is_active` | boolean | Whether the vendor is active | +| ↳ `country` | string | Country of the vendor | +| ↳ `total_spend_ytd` | object | Year-to-date spend with the vendor \(integer amount in the smallest currency denomination plus currency code\) | +| `nextStart` | string | Cursor for the next page of results \(null when there are no more pages\) | + +### `ramp_get_vendor` + +Retrieve a single Ramp vendor by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `vendorId` | string | Yes | ID of the vendor to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `vendor` | object | The requested Ramp vendor | +| ↳ `id` | string | Unique identifier for the vendor | +| ↳ `name` | string | Name of the vendor | +| ↳ `state` | string | State of the vendor record | +| ↳ `is_active` | boolean | Whether the vendor is active | +| ↳ `country` | string | Country of the vendor | +| ↳ `total_spend_ytd` | object | Year-to-date spend with the vendor \(integer amount in the smallest currency denomination plus currency code\) | +| ↳ `total_spend_all_time` | object | All-time spend with the vendor \(integer amount in the smallest currency denomination plus currency code\) | + +### `ramp_list_entities` + +List business entities in Ramp with optional filters + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `entityName` | string | No | Filter entities by name | +| `pageSize` | number | No | Number of results per page \(between 2 and 100, default 20\) | +| `start` | string | No | Pagination cursor: the ID of the last entity from the previous page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `entities` | array | List of business entities in Ramp | +| ↳ `id` | string | Unique identifier for the entity | +| ↳ `entity_name` | string | Name of the entity | +| ↳ `currency` | string | Primary currency of the entity | +| ↳ `is_primary` | boolean | Whether this is the primary entity of the business | +| `nextStart` | string | Cursor for the next page of results \(null when there are no more pages\) | + +### `ramp_list_spend_programs` + +List spend programs in the Ramp business + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `pageSize` | number | No | Number of results per page \(between 2 and 100, default 20\) | +| `start` | string | No | Pagination cursor: the ID of the last entity from the previous page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `spendPrograms` | array | List of spend programs in the Ramp business | +| ↳ `id` | string | Unique identifier for the spend program | +| ↳ `display_name` | string | Display name of the spend program | +| ↳ `description` | string | Description of the spend program | +| ↳ `is_shareable` | boolean | Whether limits under this program can be shared | +| ↳ `permitted_spend_types` | object | Spend types permitted by the program \(card and/or reimbursement\) | +| ↳ `restrictions` | object | Spending restrictions of the program | +| `nextStart` | string | Cursor for the next page of results \(null when there are no more pages\) | + +### `ramp_get_spend_program` + +Retrieve a single Ramp spend program by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `spendProgramId` | string | Yes | ID of the spend program to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `spendProgram` | object | The requested Ramp spend program | +| ↳ `id` | string | Unique identifier for the spend program | +| ↳ `display_name` | string | Display name of the spend program | +| ↳ `description` | string | Description of the spend program | +| ↳ `is_shareable` | boolean | Whether limits under this program can be shared | +| ↳ `permitted_spend_types` | object | Spend types permitted by the program \(card and/or reimbursement\) | +| ↳ `restrictions` | object | Spending restrictions of the program | + +### `ramp_get_business` + +Retrieve information about the authorized Ramp business + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `business` | object | The authorized Ramp business | +| ↳ `id` | string | Unique identifier for the business | +| ↳ `business_name_legal` | string | Legal name of the business | +| ↳ `business_name_on_card` | string | Business name shown on cards | +| ↳ `active` | boolean | Whether the business account is active | +| ↳ `created_time` | string | When the business account was created | +| ↳ `is_reimbursements_enabled` | boolean | Whether reimbursements are enabled for the business | +| ↳ `website` | string | Company website URL | +| ↳ `phone` | string | Primary contact phone number | + +### `ramp_get_business_balance` + +Retrieve the current balance and limits of the authorized Ramp business + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `balance` | object | Balance and limit details for the Ramp business | +| ↳ `balance_including_pending` | number | Total balance including pending transactions, in U.S. dollars | +| ↳ `card_balance_including_pending` | number | Card balance including pending transactions, in U.S. dollars | +| ↳ `card_balance_excluding_pending` | number | Card balance excluding pending transactions, in U.S. dollars | +| ↳ `card_limit` | number | Total card limit in U.S. dollars | +| ↳ `available_card_limit` | number | Remaining available card limit in U.S. dollars | +| ↳ `card_limit_amount` | object | Canonical card limit \(integer amount in the smallest currency denomination plus currency code\) | +| ↳ `available_card_limit_amount` | object | Canonical available card limit \(integer amount in the smallest currency denomination plus currency code\) | + +### `ramp_list_receipts` + +List receipts in Ramp with optional filters + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `transactionId` | string | No | Filter receipts by transaction ID | +| `fromDate` | string | No | Only include receipts for transactions that occurred after this ISO 8601 timestamp | +| `toDate` | string | No | Only include receipts for transactions that occurred before this ISO 8601 timestamp | +| `pageSize` | number | No | Number of results per page \(between 2 and 100, default 20\) | +| `start` | string | No | Pagination cursor: the ID of the last entity from the previous page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `receipts` | array | List of Ramp receipts | +| ↳ `id` | string | Unique identifier for the receipt | +| ↳ `receipt_url` | string | Pre-signed URL to download the receipt image \(valid for one hour\) | +| ↳ `transaction_id` | string | Transaction the receipt is attached to | +| ↳ `user_id` | string | User who uploaded the receipt | +| ↳ `created_at` | string | When the receipt was created | +| `nextStart` | string | Cursor for the next page of results \(null when there are no more pages\) | + +### `ramp_get_receipt` + +Retrieve a single Ramp receipt by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `receiptId` | string | Yes | ID of the receipt to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `receipt` | object | The requested Ramp receipt | +| ↳ `id` | string | Unique identifier for the receipt | +| ↳ `receipt_url` | string | Pre-signed URL to download the receipt image \(valid for one hour\) | +| ↳ `transaction_id` | string | Transaction the receipt is attached to | +| ↳ `user_id` | string | User who uploaded the receipt | +| ↳ `created_at` | string | When the receipt was created | + +### `ramp_upload_receipt` + +Upload a receipt image to Ramp and optionally attach it to a transaction. When no transaction is provided, Ramp matches the receipt to the most relevant transaction automatically. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `userId` | string | Yes | ID of the Ramp user to associate with the receipt | +| `transactionId` | string | No | ID of the transaction to attach the receipt to | +| `file` | file | Yes | The receipt image or PDF to upload | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `receiptId` | string | Unique identifier of the uploaded receipt | + + diff --git a/apps/sim/app/api/tools/ramp/upload-receipt/route.ts b/apps/sim/app/api/tools/ramp/upload-receipt/route.ts new file mode 100644 index 0000000000..a16c92e54c --- /dev/null +++ b/apps/sim/app/api/tools/ramp/upload-receipt/route.ts @@ -0,0 +1,141 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' +import { type NextRequest, NextResponse } from 'next/server' +import { rampUploadReceiptContract } from '@/lib/api/contracts/tools/ramp' +import { parseRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils' +import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' +import { assertToolFileAccess } from '@/app/api/files/authorization' +import { extractRampError } from '@/tools/ramp/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('RampUploadReceiptAPI') + +const RAMP_RECEIPTS_URL = 'https://api.ramp.com/developer/v1/receipts' + +/** + * Builds the multipart body for Ramp's receipt upload endpoint. Ramp expects + * metadata parts with `Content-Disposition: form-data` and the receipt image + * as a part named `receipt` with `Content-Disposition: attachment`. + */ +function buildReceiptMultipartBody( + boundary: string, + fields: Record, + file: { name: string; type: string; buffer: Buffer } +): Buffer { + const parts: Buffer[] = [] + + for (const [name, value] of Object.entries(fields)) { + const safeValue = value.replace(/[\r\n]/g, '') + parts.push( + Buffer.from( + `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${safeValue}\r\n` + ) + ) + } + + const safeFileName = file.name.replace(/[\r\n"]/g, '_') + const safeContentType = file.type.replace(/[\r\n]/g, '') || 'application/octet-stream' + parts.push( + Buffer.from( + `--${boundary}\r\nContent-Disposition: attachment; name="receipt"; filename="${safeFileName}"\r\nContent-Type: ${safeContentType}\r\n\r\n` + ) + ) + parts.push(file.buffer) + parts.push(Buffer.from(`\r\n--${boundary}--\r\n`)) + + return Buffer.concat(parts) +} + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + + if (!authResult.success || !authResult.userId) { + logger.warn(`[${requestId}] Unauthorized Ramp receipt upload attempt: ${authResult.error}`) + return NextResponse.json( + { success: false, error: authResult.error || 'Authentication required' }, + { status: 401 } + ) + } + + const parsed = await parseRequest(rampUploadReceiptContract, request, {}) + if (!parsed.success) return parsed.response + const validatedData = parsed.data.body + + const userFiles = processFilesToUserFiles( + [validatedData.file as RawFileInput], + requestId, + logger + ) + + if (userFiles.length === 0) { + return NextResponse.json({ success: false, error: 'Invalid file input' }, { status: 400 }) + } + + const userFile = userFiles[0] + logger.info( + `[${requestId}] Downloading receipt file: ${userFile.name} (${userFile.size} bytes)` + ) + + const denied = await assertToolFileAccess(userFile.key, authResult.userId, requestId, logger) + if (denied) return denied + const fileBuffer = await downloadFileFromStorage(userFile, requestId, logger) + + const fields: Record = { + idempotency_key: generateId(), + user_id: validatedData.userId, + } + if (validatedData.transactionId) { + fields.transaction_id = validatedData.transactionId + } + + const boundary = `----sim-ramp-receipt-${generateId()}` + const body = buildReceiptMultipartBody(boundary, fields, { + name: userFile.name, + type: userFile.type || 'application/octet-stream', + buffer: fileBuffer, + }) + + logger.info(`[${requestId}] Uploading receipt to Ramp (${fileBuffer.length} bytes)`) + + const response = await fetch(RAMP_RECEIPTS_URL, { + method: 'POST', + headers: { + Authorization: `Bearer ${validatedData.accessToken}`, + 'Content-Type': `multipart/form-data; boundary=${boundary}`, + }, + body: new Uint8Array(body), + }) + + const data = await response.json().catch(() => ({})) + + if (!response.ok) { + const errorMessage = extractRampError(data, 'Failed to upload receipt to Ramp') + logger.error(`[${requestId}] Ramp API error:`, { status: response.status, data }) + return NextResponse.json({ success: false, error: errorMessage }, { status: response.status }) + } + + logger.info(`[${requestId}] Receipt uploaded successfully: ${data.id}`) + + return NextResponse.json({ + success: true, + output: { + receiptId: data.id, + }, + }) + } catch (error) { + logger.error(`[${requestId}] Unexpected error:`, error) + return NextResponse.json( + { success: false, error: getErrorMessage(error, 'Unknown error') }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/blocks/blocks/ramp.ts b/apps/sim/blocks/blocks/ramp.ts new file mode 100644 index 0000000000..9662333c17 --- /dev/null +++ b/apps/sim/blocks/blocks/ramp.ts @@ -0,0 +1,590 @@ +import { RampIcon } from '@/components/icons' +import { getScopesForService } from '@/lib/oauth/utils' +import type { BlockConfig, BlockMeta } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' +import type { RampResponse } from '@/tools/ramp/types' + +const RAMP_OPERATIONS = [ + 'ramp_list_transactions', + 'ramp_get_transaction', + 'ramp_list_users', + 'ramp_get_user', + 'ramp_list_cards', + 'ramp_get_card', + 'ramp_list_limits', + 'ramp_get_limit', + 'ramp_list_reimbursements', + 'ramp_get_reimbursement', + 'ramp_list_bills', + 'ramp_get_bill', + 'ramp_list_departments', + 'ramp_get_department', + 'ramp_create_department', + 'ramp_list_vendors', + 'ramp_get_vendor', + 'ramp_list_entities', + 'ramp_list_spend_programs', + 'ramp_get_spend_program', + 'ramp_get_business', + 'ramp_get_business_balance', + 'ramp_list_receipts', + 'ramp_get_receipt', + 'ramp_upload_receipt', +] as const + +const RAMP_LIST_OPERATIONS = [ + 'ramp_list_transactions', + 'ramp_list_users', + 'ramp_list_cards', + 'ramp_list_limits', + 'ramp_list_reimbursements', + 'ramp_list_bills', + 'ramp_list_departments', + 'ramp_list_vendors', + 'ramp_list_entities', + 'ramp_list_spend_programs', + 'ramp_list_receipts', +] + +export const RampBlock: BlockConfig = { + type: 'ramp', + name: 'Ramp', + description: 'Manage spend, transactions, reimbursements, bills, and receipts in Ramp', + authMode: AuthMode.OAuth, + longDescription: + 'Integrate Ramp into your workflow to automate corporate spend operations. List and inspect card transactions, users, cards, spend limits, reimbursements, bills, departments, and vendors, and upload receipts directly to transactions.', + docsLink: 'https://docs.sim.ai/integrations/ramp', + category: 'tools', + integrationType: IntegrationType.Commerce, + icon: RampIcon, + bgColor: '#E4F222', + iconColor: '#E4F222', + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Transactions', id: 'ramp_list_transactions' }, + { label: 'Get Transaction', id: 'ramp_get_transaction' }, + { label: 'List Users', id: 'ramp_list_users' }, + { label: 'Get User', id: 'ramp_get_user' }, + { label: 'List Cards', id: 'ramp_list_cards' }, + { label: 'Get Card', id: 'ramp_get_card' }, + { label: 'List Limits', id: 'ramp_list_limits' }, + { label: 'Get Limit', id: 'ramp_get_limit' }, + { label: 'List Reimbursements', id: 'ramp_list_reimbursements' }, + { label: 'Get Reimbursement', id: 'ramp_get_reimbursement' }, + { label: 'List Bills', id: 'ramp_list_bills' }, + { label: 'Get Bill', id: 'ramp_get_bill' }, + { label: 'List Departments', id: 'ramp_list_departments' }, + { label: 'Get Department', id: 'ramp_get_department' }, + { label: 'Create Department', id: 'ramp_create_department' }, + { label: 'List Vendors', id: 'ramp_list_vendors' }, + { label: 'Get Vendor', id: 'ramp_get_vendor' }, + { label: 'List Entities', id: 'ramp_list_entities' }, + { label: 'List Spend Programs', id: 'ramp_list_spend_programs' }, + { label: 'Get Spend Program', id: 'ramp_get_spend_program' }, + { label: 'Get Business', id: 'ramp_get_business' }, + { label: 'Get Business Balance', id: 'ramp_get_business_balance' }, + { label: 'List Receipts', id: 'ramp_list_receipts' }, + { label: 'Get Receipt', id: 'ramp_get_receipt' }, + { label: 'Upload Receipt', id: 'ramp_upload_receipt' }, + ], + value: () => 'ramp_list_transactions', + }, + { + id: 'credential', + title: 'Ramp Account', + type: 'oauth-input', + canonicalParamId: 'oauthCredential', + mode: 'basic', + serviceId: 'ramp', + requiredScopes: getScopesForService('ramp'), + placeholder: 'Select Ramp account', + required: true, + }, + { + id: 'manualCredential', + title: 'Ramp Account', + type: 'short-input', + canonicalParamId: 'oauthCredential', + mode: 'advanced', + placeholder: 'Enter credential ID', + required: true, + }, + // Resource IDs + { + id: 'transactionId', + title: 'Transaction ID', + type: 'short-input', + placeholder: 'Enter transaction ID', + condition: { + field: 'operation', + value: ['ramp_get_transaction', 'ramp_list_receipts', 'ramp_upload_receipt'], + }, + required: { field: 'operation', value: ['ramp_get_transaction'] }, + }, + { + id: 'userId', + title: 'User ID', + type: 'short-input', + placeholder: 'Enter user ID', + condition: { + field: 'operation', + value: [ + 'ramp_list_transactions', + 'ramp_get_user', + 'ramp_list_cards', + 'ramp_list_limits', + 'ramp_list_reimbursements', + 'ramp_upload_receipt', + ], + }, + required: { field: 'operation', value: ['ramp_get_user', 'ramp_upload_receipt'] }, + }, + { + id: 'cardId', + title: 'Card ID', + type: 'short-input', + placeholder: 'Enter card ID', + condition: { + field: 'operation', + value: ['ramp_list_transactions', 'ramp_get_card', 'ramp_list_limits'], + }, + required: { field: 'operation', value: ['ramp_get_card'] }, + }, + { + id: 'limitId', + title: 'Limit ID', + type: 'short-input', + placeholder: 'Enter spend limit ID', + condition: { field: 'operation', value: 'ramp_get_limit' }, + required: true, + }, + { + id: 'reimbursementId', + title: 'Reimbursement ID', + type: 'short-input', + placeholder: 'Enter reimbursement ID', + condition: { field: 'operation', value: 'ramp_get_reimbursement' }, + required: true, + }, + { + id: 'spendProgramId', + title: 'Spend Program ID', + type: 'short-input', + placeholder: 'Enter spend program ID', + condition: { field: 'operation', value: 'ramp_get_spend_program' }, + required: true, + }, + { + id: 'departmentName', + title: 'Department Name', + type: 'short-input', + placeholder: 'Enter department name', + condition: { field: 'operation', value: 'ramp_create_department' }, + required: true, + }, + { + id: 'billId', + title: 'Bill ID', + type: 'short-input', + placeholder: 'Enter bill ID', + condition: { field: 'operation', value: 'ramp_get_bill' }, + required: true, + }, + { + id: 'receiptId', + title: 'Receipt ID', + type: 'short-input', + placeholder: 'Enter receipt ID', + condition: { field: 'operation', value: 'ramp_get_receipt' }, + required: true, + }, + // Receipt upload + { + id: 'uploadReceiptFile', + title: 'Receipt File', + type: 'file-upload', + canonicalParamId: 'file', + placeholder: 'Upload receipt image or PDF', + mode: 'basic', + multiple: false, + acceptedTypes: 'image/*,application/pdf', + required: true, + condition: { field: 'operation', value: 'ramp_upload_receipt' }, + }, + { + id: 'receiptFileRef', + title: 'Receipt File', + type: 'short-input', + canonicalParamId: 'file', + placeholder: 'Reference file from previous blocks', + mode: 'advanced', + required: true, + condition: { field: 'operation', value: 'ramp_upload_receipt' }, + }, + // Filters + { + id: 'departmentId', + title: 'Department ID', + type: 'short-input', + placeholder: 'Enter department ID', + condition: { + field: 'operation', + value: ['ramp_list_transactions', 'ramp_list_users', 'ramp_get_department'], + }, + required: { field: 'operation', value: ['ramp_get_department'] }, + }, + { + id: 'state', + title: 'Transaction State', + type: 'dropdown', + options: [ + { label: 'All (including declined)', id: 'ALL' }, + { label: 'Cleared', id: 'CLEARED' }, + { label: 'Completion', id: 'COMPLETION' }, + { label: 'Declined', id: 'DECLINED' }, + { label: 'Error', id: 'ERROR' }, + { label: 'Pending', id: 'PENDING' }, + { label: 'Pending initiation', id: 'PENDING_INITIATION' }, + ], + mode: 'advanced', + condition: { field: 'operation', value: 'ramp_list_transactions' }, + }, + { + id: 'merchantId', + title: 'Merchant ID', + type: 'short-input', + placeholder: 'Filter by merchant ID', + mode: 'advanced', + condition: { field: 'operation', value: 'ramp_list_transactions' }, + }, + { + id: 'minAmount', + title: 'Minimum Amount (USD)', + type: 'short-input', + placeholder: '100', + mode: 'advanced', + condition: { field: 'operation', value: 'ramp_list_transactions' }, + }, + { + id: 'maxAmount', + title: 'Maximum Amount (USD)', + type: 'short-input', + placeholder: '1000', + mode: 'advanced', + condition: { field: 'operation', value: 'ramp_list_transactions' }, + }, + { + id: 'fromDate', + title: 'From Date', + type: 'short-input', + placeholder: '2025-01-01T00:00:00Z', + condition: { + field: 'operation', + value: ['ramp_list_transactions', 'ramp_list_reimbursements', 'ramp_list_receipts'], + }, + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp based on the user's description. +The timestamp should be in the format: YYYY-MM-DDTHH:MM:SSZ (UTC timezone). +Examples: +- "start of this month" -> First day of current month at 00:00:00Z +- "30 days ago" -> Calculate 30 days before now +- "start of the quarter" -> First day of the current quarter at 00:00:00Z + +Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the start of the range (e.g., "30 days ago")...', + generationType: 'timestamp', + }, + }, + { + id: 'toDate', + title: 'To Date', + type: 'short-input', + placeholder: '2025-12-31T23:59:59Z', + condition: { + field: 'operation', + value: ['ramp_list_transactions', 'ramp_list_reimbursements', 'ramp_list_receipts'], + }, + wandConfig: { + enabled: true, + prompt: `Generate an ISO 8601 timestamp based on the user's description. +The timestamp should be in the format: YYYY-MM-DDTHH:MM:SSZ (UTC timezone). +Examples: +- "now" -> The current date and time +- "end of this month" -> Last day of current month at 23:59:59Z +- "yesterday" -> Yesterday at 23:59:59Z + +Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the end of the range (e.g., "now", "end of month")...', + generationType: 'timestamp', + }, + }, + { + id: 'email', + title: 'Email', + type: 'short-input', + placeholder: 'Filter by email address', + condition: { field: 'operation', value: 'ramp_list_users' }, + }, + { + id: 'displayName', + title: 'Card Display Name', + type: 'short-input', + placeholder: 'Filter by card display name', + mode: 'advanced', + condition: { field: 'operation', value: 'ramp_list_cards' }, + }, + { + id: 'vendorId', + title: 'Vendor ID', + type: 'short-input', + placeholder: 'Enter vendor ID', + condition: { field: 'operation', value: ['ramp_list_bills', 'ramp_get_vendor'] }, + required: { field: 'operation', value: ['ramp_get_vendor'] }, + }, + { + id: 'vendorName', + title: 'Vendor Name', + type: 'short-input', + placeholder: 'Filter by vendor name', + condition: { field: 'operation', value: 'ramp_list_vendors' }, + }, + { + id: 'entityName', + title: 'Entity Name', + type: 'short-input', + placeholder: 'Filter by entity name', + condition: { field: 'operation', value: 'ramp_list_entities' }, + }, + // Pagination (shared across all list operations) + { + id: 'pageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + mode: 'advanced', + condition: { field: 'operation', value: RAMP_LIST_OPERATIONS }, + }, + { + id: 'start', + title: 'Start Cursor', + type: 'short-input', + placeholder: 'ID of the last entity from the previous page', + mode: 'advanced', + condition: { field: 'operation', value: RAMP_LIST_OPERATIONS }, + }, + ], + tools: { + access: [ + 'ramp_list_transactions', + 'ramp_get_transaction', + 'ramp_list_users', + 'ramp_get_user', + 'ramp_list_cards', + 'ramp_get_card', + 'ramp_list_limits', + 'ramp_get_limit', + 'ramp_list_reimbursements', + 'ramp_get_reimbursement', + 'ramp_list_bills', + 'ramp_get_bill', + 'ramp_list_departments', + 'ramp_get_department', + 'ramp_create_department', + 'ramp_list_vendors', + 'ramp_get_vendor', + 'ramp_list_entities', + 'ramp_list_spend_programs', + 'ramp_get_spend_program', + 'ramp_get_business', + 'ramp_get_business_balance', + 'ramp_list_receipts', + 'ramp_get_receipt', + 'ramp_upload_receipt', + ], + config: { + tool: (params) => + (RAMP_OPERATIONS as readonly string[]).includes(params.operation) + ? params.operation + : 'ramp_list_transactions', + params: (params) => { + const result: Record = {} + if (params.pageSize) result.pageSize = Number(params.pageSize) + if (params.minAmount) result.minAmount = Number(params.minAmount) + if (params.maxAmount) result.maxAmount = Number(params.maxAmount) + const normalizedFile = normalizeFileInput(params.file, { single: true }) + if (normalizedFile) { + result.file = normalizedFile + } + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + oauthCredential: { type: 'string', description: 'Ramp OAuth credential' }, + transactionId: { type: 'string', description: 'Transaction ID' }, + userId: { type: 'string', description: 'User ID' }, + cardId: { type: 'string', description: 'Card ID' }, + limitId: { type: 'string', description: 'Spend limit ID' }, + reimbursementId: { type: 'string', description: 'Reimbursement ID' }, + billId: { type: 'string', description: 'Bill ID' }, + receiptId: { type: 'string', description: 'Receipt ID' }, + spendProgramId: { type: 'string', description: 'Spend program ID' }, + departmentName: { type: 'string', description: 'Name of the department to create' }, + file: { type: 'json', description: 'Receipt file to upload (canonical param)' }, + departmentId: { type: 'string', description: 'Department ID' }, + state: { type: 'string', description: 'Transaction state filter' }, + merchantId: { type: 'string', description: 'Merchant ID filter' }, + minAmount: { type: 'number', description: 'Minimum transaction amount in U.S. dollars' }, + maxAmount: { type: 'number', description: 'Maximum transaction amount in U.S. dollars' }, + fromDate: { type: 'string', description: 'Start of the date range (ISO 8601)' }, + toDate: { type: 'string', description: 'End of the date range (ISO 8601)' }, + email: { type: 'string', description: 'Email address filter' }, + displayName: { type: 'string', description: 'Card display name filter' }, + vendorId: { type: 'string', description: 'Vendor ID' }, + vendorName: { type: 'string', description: 'Vendor name filter' }, + entityName: { type: 'string', description: 'Entity name filter' }, + pageSize: { type: 'number', description: 'Number of results per page (2-100)' }, + start: { type: 'string', description: 'Pagination cursor' }, + }, + outputs: { + // List outputs + transactions: { type: 'json', description: 'List of Ramp card transactions' }, + users: { type: 'json', description: 'List of users in the Ramp business' }, + cards: { type: 'json', description: 'List of Ramp corporate cards' }, + limits: { type: 'json', description: 'List of Ramp spend limits' }, + reimbursements: { type: 'json', description: 'List of Ramp reimbursements' }, + bills: { type: 'json', description: 'List of Ramp bills' }, + departments: { type: 'json', description: 'List of departments' }, + vendors: { type: 'json', description: 'List of Ramp vendors' }, + entities: { type: 'json', description: 'List of business entities' }, + spendPrograms: { type: 'json', description: 'List of spend programs' }, + receipts: { type: 'json', description: 'List of Ramp receipts' }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + }, + // Single-resource outputs + transaction: { type: 'json', description: 'The requested transaction' }, + user: { type: 'json', description: 'The requested user' }, + card: { type: 'json', description: 'The requested card' }, + limit: { type: 'json', description: 'The requested spend limit' }, + reimbursement: { type: 'json', description: 'The requested reimbursement' }, + bill: { type: 'json', description: 'The requested bill' }, + department: { type: 'json', description: 'The requested or created department' }, + vendor: { type: 'json', description: 'The requested vendor' }, + spendProgram: { type: 'json', description: 'The requested spend program' }, + business: { type: 'json', description: 'The authorized Ramp business' }, + balance: { type: 'json', description: 'Balance and limits of the Ramp business' }, + receipt: { type: 'json', description: 'The requested receipt' }, + // Upload output + receiptId: { type: 'string', description: 'Unique identifier of the uploaded receipt' }, + }, +} + +export const RampBlockMeta = { + tags: ['payments', 'automation'], + templates: [ + { + icon: RampIcon, + title: 'Ramp spend digest to Slack', + prompt: + 'Build a scheduled workflow that lists Ramp transactions from the last 24 hours, summarizes total spend by department and the largest purchases with an agent, and posts the digest to a finance Slack channel.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['finance', 'reporting'], + alsoIntegrations: ['slack'], + }, + { + icon: RampIcon, + title: 'Ramp receipt auto-uploader', + prompt: + 'Create a workflow that watches a shared inbox for emailed receipts, extracts the receipt attachment, matches it to the right Ramp user by sender email with List Users, and uploads the receipt to Ramp so it attaches to the matching transaction.', + modules: ['agent', 'files', 'workflows'], + category: 'operations', + tags: ['finance', 'automation'], + }, + { + icon: RampIcon, + title: 'Ramp missing-receipt chaser', + prompt: + 'Build a scheduled workflow that lists cleared Ramp transactions over $75 from the past week, filters out transactions that already have receipts, looks up each cardholder with Get User, and sends each one a Slack reminder to upload their receipt.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['finance', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: RampIcon, + title: 'Ramp transactions to a table', + prompt: + 'Create a scheduled workflow that pages through Ramp transactions since the last run using the nextStart cursor and appends each transaction with merchant, amount, state, and cardholder to a spend-tracking table for analysis.', + modules: ['scheduled', 'tables', 'workflows'], + category: 'operations', + tags: ['finance', 'sync'], + }, + { + icon: RampIcon, + title: 'Ramp reimbursement approvals monitor', + prompt: + 'Build a scheduled workflow that lists Ramp reimbursements created in the last week, flags ones that have been pending longer than three days, and posts a summary with amounts and requesters to an operations Slack channel.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['finance', 'monitoring'], + alsoIntegrations: ['slack'], + }, + { + icon: RampIcon, + title: 'Ramp bills due-date tracker', + prompt: + 'Create a scheduled workflow that lists open Ramp bills, identifies bills due within the next seven days, and writes vendor, invoice number, amount, and due date to a payables table while alerting finance about anything overdue.', + modules: ['scheduled', 'tables', 'agent', 'workflows'], + category: 'operations', + tags: ['finance', 'reporting'], + }, + { + icon: RampIcon, + title: 'Ramp vendor spend review', + prompt: + 'Build a scheduled monthly workflow that lists Ramp vendors, pulls year-to-date spend for each, asks an agent to flag vendors with unusual growth or overlapping categories, and writes the review to a vendor-management table.', + modules: ['scheduled', 'tables', 'agent', 'workflows'], + category: 'operations', + tags: ['finance', 'analysis'], + }, + ], + skills: [ + { + name: 'audit-ramp-spend', + description: + 'Pull Ramp transactions for a date range and summarize spend by merchant, department, or cardholder.', + content: + '# Audit Ramp Spend\n\nReview card spend in Ramp over a period.\n\n## Steps\n1. Call List Transactions with fromDate and toDate covering the requested period. Add userId, cardId, or departmentId filters when the question is scoped to a person, card, or team.\n2. Page through results: while nextStart is not null, call List Transactions again passing it as the start parameter.\n3. Aggregate amounts by the requested dimension (merchant, department, or cardholder) and identify outliers or policy concerns.\n\n## Output\nReport total spend, a breakdown by the requested dimension, and the largest individual transactions with merchant, amount, and date.', + }, + { + name: 'upload-receipt-to-ramp', + description: + 'Upload a receipt image or PDF to Ramp and attach it to the right transaction and user.', + content: + '# Upload Receipt to Ramp\n\nAttach a receipt to a Ramp transaction.\n\n## Steps\n1. Identify the Ramp user the receipt belongs to. If only an email is known, call List Users with the email filter to find the user ID.\n2. If a specific transaction is known, find its ID with List Transactions (filter by user, date, or amount) or use the provided ID directly.\n3. Call Upload Receipt with the file and the user ID. Pass the transaction ID when known; otherwise Ramp will auto-match the receipt to the most relevant transaction.\n\n## Output\nReturn the uploaded receipt ID and state whether it was attached to a specific transaction or left for auto-matching.', + }, + { + name: 'find-ramp-transactions-missing-receipts', + description: + 'Find cleared Ramp transactions above a threshold that have no receipts attached.', + content: + '# Find Ramp Transactions Missing Receipts\n\nIdentify transactions that need receipts for compliance.\n\n## Steps\n1. Call List Transactions with state CLEARED and the requested date range; use minAmount to apply the policy threshold.\n2. Page through all results using nextStart, and keep transactions whose receipts array is empty.\n3. For each match, call Get User on the cardholder user ID to get their name and email for follow-up.\n\n## Output\nReturn a list of transactions missing receipts with merchant, amount, date, and the cardholder name and email to contact.', + }, + { + name: 'review-ramp-bills', + description: 'List Ramp bills, check statuses and due dates, and flag what needs payment.', + content: + '# Review Ramp Bills\n\nCheck the state of payables in Ramp.\n\n## Steps\n1. Call List Bills, optionally filtered by vendorId when reviewing a single vendor. Page through all results with nextStart.\n2. Group bills by status and compare due_at against today to find upcoming and overdue bills. Bill amounts are canonical: divide amount.amount by 100 for USD dollars.\n3. Use Get Bill on anything that needs line-item detail before recommending action.\n\n## Output\nReturn bills due soon and overdue bills with vendor, invoice number, amount, and due date, plus a one-line recommendation for each.', + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index bc7d8b42bb..6f63dfc313 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -227,6 +227,7 @@ import { PulseBlock, PulseBlockMeta, PulseV2Block } from '@/blocks/blocks/pulse' import { QdrantBlock, QdrantBlockMeta } from '@/blocks/blocks/qdrant' import { QuiverBlock, QuiverBlockMeta } from '@/blocks/blocks/quiver' import { RailwayBlock, RailwayBlockMeta } from '@/blocks/blocks/railway' +import { RampBlock, RampBlockMeta } from '@/blocks/blocks/ramp' import { RB2BBlock, RB2BBlockMeta } from '@/blocks/blocks/rb2b' import { RDSBlock, RDSBlockMeta } from '@/blocks/blocks/rds' import { RedditBlock, RedditBlockMeta } from '@/blocks/blocks/reddit' @@ -518,6 +519,7 @@ const BLOCK_REGISTRY: Record = { qdrant: QdrantBlock, quiver: QuiverBlock, railway: RailwayBlock, + ramp: RampBlock, rb2b: RB2BBlock, rds: RDSBlock, reddit: RedditBlock, @@ -780,6 +782,7 @@ const BLOCK_META_REGISTRY: Record = { qdrant: QdrantBlockMeta, quiver: QuiverBlockMeta, railway: RailwayBlockMeta, + ramp: RampBlockMeta, rb2b: RB2BBlockMeta, rds: RDSBlockMeta, reddit: RedditBlockMeta, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 6161069bef..31d3c50806 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -1643,6 +1643,17 @@ export function RB2BIcon(props: SVGProps) { ) } +export function RampIcon(props: SVGProps) { + return ( + + + + ) +} + export function RedditIcon(props: SVGProps) { return ( diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index c43a7f56ce..3f8786fe98 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -690,6 +690,7 @@ export const auth = betterAuth({ 'airtable', 'box', 'dropbox', + 'ramp', 'salesforce', 'wealthbox', 'zoom', @@ -2494,6 +2495,54 @@ export const auth = betterAuth({ }, }, + { + providerId: 'ramp', + clientId: env.RAMP_CLIENT_ID as string, + clientSecret: env.RAMP_CLIENT_SECRET as string, + authorizationUrl: 'https://app.ramp.com/v1/authorize', + tokenUrl: 'https://api.ramp.com/developer/v1/token', + scopes: getCanonicalScopesForProvider('ramp'), + responseType: 'code', + authentication: 'basic', + redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/ramp`, + getUserInfo: async (tokens) => { + try { + const response = await fetch('https://api.ramp.com/developer/v1/business', { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }) + + if (!response.ok) { + const errorText = await response.text() + logger.error('Ramp API error:', { + status: response.status, + statusText: response.statusText, + body: errorText, + }) + throw new Error(`Ramp API error: ${response.status} ${response.statusText}`) + } + + const data = await response.json() + + return { + // Ramp authorizes a business, not an end user, and exposes no + // user identity endpoint - synthesize a stable identity from + // the business record. + id: `${data.id}-${generateId()}`, + email: `${data.id}@ramp.business`, + name: data.business_name_legal || data.business_name_on_card || 'Ramp Business', + emailVerified: true, + createdAt: new Date(), + updatedAt: new Date(), + } + } catch (error) { + logger.error('Error in Ramp getUserInfo:', error) + throw error + } + }, + }, + { providerId: 'asana', clientId: env.ASANA_CLIENT_ID as string, diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index c01826cdfe..72579a3d3e 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -356,6 +356,8 @@ export const env = createEnv({ DROPBOX_CLIENT_SECRET: z.string().optional(), // Dropbox OAuth client secret SLACK_CLIENT_ID: z.string().optional(), // Slack OAuth client ID SLACK_CLIENT_SECRET: z.string().optional(), // Slack OAuth client secret + RAMP_CLIENT_ID: z.string().optional(), // Ramp OAuth client ID + RAMP_CLIENT_SECRET: z.string().optional(), // Ramp OAuth client secret REDDIT_CLIENT_ID: z.string().optional(), // Reddit OAuth client ID REDDIT_CLIENT_SECRET: z.string().optional(), // Reddit OAuth client secret WEBFLOW_CLIENT_ID: z.string().optional(), // Webflow OAuth client ID diff --git a/apps/sim/lib/integrations/icon-mapping.ts b/apps/sim/lib/integrations/icon-mapping.ts index 7344cb8ee1..c4bfbc5d1b 100644 --- a/apps/sim/lib/integrations/icon-mapping.ts +++ b/apps/sim/lib/integrations/icon-mapping.ts @@ -157,6 +157,7 @@ import { QdrantIcon, QuiverIcon, RailwayIcon, + RampIcon, RB2BIcon, RDSIcon, RedditIcon, @@ -375,6 +376,7 @@ export const blockTypeToIconMap: Record = { qdrant: QdrantIcon, quiver: QuiverIcon, railway: RailwayIcon, + ramp: RampIcon, rb2b: RB2BIcon, rds: RDSIcon, reddit: RedditIcon, diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index 72301855dd..d717c26af9 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -11594,6 +11594,125 @@ "integrationType": "devops", "tags": ["cloud", "ci-cd"] }, + { + "type": "ramp", + "slug": "ramp", + "name": "Ramp", + "description": "Manage spend, transactions, reimbursements, bills, and receipts in Ramp", + "longDescription": "Integrate Ramp into your workflow to automate corporate spend operations. List and inspect card transactions, users, cards, spend limits, reimbursements, bills, departments, and vendors, and upload receipts directly to transactions.", + "bgColor": "#E4F222", + "iconName": "RampIcon", + "docsUrl": "https://docs.sim.ai/integrations/ramp", + "operations": [ + { + "name": "List Transactions", + "description": "List card transactions in Ramp with optional filters" + }, + { + "name": "Get Transaction", + "description": "Retrieve a single Ramp card transaction by ID" + }, + { + "name": "List Users", + "description": "List users in the Ramp business with optional filters" + }, + { + "name": "Get User", + "description": "Retrieve a single Ramp user by ID" + }, + { + "name": "List Cards", + "description": "List corporate cards in Ramp with optional filters" + }, + { + "name": "Get Card", + "description": "Retrieve a single Ramp corporate card by ID" + }, + { + "name": "List Limits", + "description": "List spend limits in Ramp with optional filters" + }, + { + "name": "Get Limit", + "description": "Retrieve a single Ramp spend limit by ID" + }, + { + "name": "List Reimbursements", + "description": "List reimbursements in Ramp with optional filters" + }, + { + "name": "Get Reimbursement", + "description": "Retrieve a single Ramp reimbursement by ID" + }, + { + "name": "List Bills", + "description": "List bills in Ramp with optional filters" + }, + { + "name": "Get Bill", + "description": "Retrieve a single Ramp bill by ID" + }, + { + "name": "List Departments", + "description": "List departments in the Ramp business" + }, + { + "name": "Get Department", + "description": "Retrieve a single Ramp department by ID" + }, + { + "name": "Create Department", + "description": "Create a new department in the Ramp business" + }, + { + "name": "List Vendors", + "description": "List vendors in Ramp with optional filters" + }, + { + "name": "Get Vendor", + "description": "Retrieve a single Ramp vendor by ID" + }, + { + "name": "List Entities", + "description": "List business entities in Ramp with optional filters" + }, + { + "name": "List Spend Programs", + "description": "List spend programs in the Ramp business" + }, + { + "name": "Get Spend Program", + "description": "Retrieve a single Ramp spend program by ID" + }, + { + "name": "Get Business", + "description": "Retrieve information about the authorized Ramp business" + }, + { + "name": "Get Business Balance", + "description": "Retrieve the current balance and limits of the authorized Ramp business" + }, + { + "name": "List Receipts", + "description": "List receipts in Ramp with optional filters" + }, + { + "name": "Get Receipt", + "description": "Retrieve a single Ramp receipt by ID" + }, + { + "name": "Upload Receipt", + "description": "Upload a receipt image to Ramp and optionally attach it to a transaction. When no transaction is provided, Ramp matches the receipt to the most relevant transaction automatically." + } + ], + "operationCount": 25, + "triggers": [], + "triggerCount": 0, + "authType": "oauth", + "category": "tools", + "integrationType": "commerce", + "tags": ["payments", "automation"] + }, { "type": "rb2b", "slug": "rb2b", diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 9cca712769..ee62a3ab3d 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -38,6 +38,7 @@ import { NotionIcon, OutlookIcon, PipedriveIcon, + RampIcon, RedditIcon, SalesforceIcon, ShopifyIcon, @@ -715,6 +716,37 @@ export const OAUTH_PROVIDERS: Record = { }, defaultService: 'slack', }, + ramp: { + name: 'Ramp', + icon: RampIcon, + services: { + ramp: { + name: 'Ramp', + description: 'Manage spend, transactions, reimbursements, bills, and receipts in Ramp.', + providerId: 'ramp', + icon: RampIcon, + baseProviderIcon: RampIcon, + scopes: [ + 'business:read', + 'transactions:read', + 'users:read', + 'cards:read', + 'limits:read', + 'reimbursements:read', + 'bills:read', + 'departments:read', + 'departments:write', + 'vendors:read', + 'entities:read', + 'spend_programs:read', + 'receipts:read', + 'receipts:write', + 'offline_access', + ], + }, + }, + defaultService: 'ramp', + }, reddit: { name: 'Reddit', icon: RedditIcon, @@ -1234,6 +1266,16 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig { supportsRefreshTokenRotation: true, } } + case 'ramp': { + const { clientId, clientSecret } = getCredentials(env.RAMP_CLIENT_ID, env.RAMP_CLIENT_SECRET) + return { + tokenEndpoint: 'https://api.ramp.com/developer/v1/token', + clientId, + clientSecret, + useBasicAuth: true, + supportsRefreshTokenRotation: true, + } + } case 'reddit': { const { clientId, clientSecret } = getCredentials( env.REDDIT_CLIENT_ID, diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index bd1de917bf..3679e1b9e7 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -47,6 +47,7 @@ export type OAuthProvider = | 'sharepoint' | 'linear' | 'slack' + | 'ramp' | 'reddit' | 'trello' | 'wealthbox' @@ -97,6 +98,7 @@ export type OAuthService = | 'outlook' | 'linear' | 'slack' + | 'ramp' | 'reddit' | 'wealthbox' | 'onedrive' diff --git a/apps/sim/lib/oauth/utils.ts b/apps/sim/lib/oauth/utils.ts index f95626781a..b836cfba1e 100644 --- a/apps/sim/lib/oauth/utils.ts +++ b/apps/sim/lib/oauth/utils.ts @@ -380,6 +380,21 @@ export const SCOPE_DESCRIPTIONS: Record = { 'sharing.read': 'View shared files and folders', 'sharing.write': 'Share files and folders with others', + // Ramp scopes (users:read and offline_access are defined above) + 'business:read': 'View business information', + 'transactions:read': 'View card transactions', + 'cards:read': 'View corporate cards', + 'limits:read': 'View spend limits', + 'reimbursements:read': 'View reimbursements', + 'bills:read': 'View bills', + 'departments:read': 'View departments', + 'departments:write': 'Create and manage departments', + 'vendors:read': 'View vendors', + 'entities:read': 'View business entities', + 'spend_programs:read': 'View spend programs', + 'receipts:read': 'View receipts', + 'receipts:write': 'Upload receipts', + // WordPress.com scopes global: 'Full access to manage WordPress.com sites, posts, pages, media, and settings', diff --git a/apps/sim/tools/ramp/create_department.ts b/apps/sim/tools/ramp/create_department.ts new file mode 100644 index 0000000000..71c67984d0 --- /dev/null +++ b/apps/sim/tools/ramp/create_department.ts @@ -0,0 +1,75 @@ +import type { RampCreateDepartmentParams, RampCreateDepartmentResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampCreateDepartmentTool: ToolConfig< + RampCreateDepartmentParams, + RampCreateDepartmentResponse +> = { + id: 'ramp_create_department', + name: 'Ramp Create Department', + description: 'Create a new department in the Ramp business', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + departmentName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the department to create', + }, + }, + + request: { + url: () => buildRampUrl('/departments'), + method: 'POST', + headers: (params) => ({ + ...buildRampHeaders(params), + 'Content-Type': 'application/json', + }), + body: (params) => ({ + name: params.departmentName, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to create Ramp department'), + output: {}, + } + } + + return { + success: true, + output: { + department: data, + }, + } + }, + + outputs: { + department: { + type: 'object', + description: 'The created Ramp department', + properties: { + id: { type: 'string', description: 'Unique identifier for the department' }, + name: { type: 'string', description: 'Name of the department' }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/get_bill.ts b/apps/sim/tools/ramp/get_bill.ts new file mode 100644 index 0000000000..1961907cb8 --- /dev/null +++ b/apps/sim/tools/ramp/get_bill.ts @@ -0,0 +1,77 @@ +import type { RampGetBillParams, RampGetBillResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetBillTool: ToolConfig = { + id: 'ramp_get_bill', + name: 'Ramp Get Bill', + description: 'Retrieve a single Ramp bill by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + billId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the bill to retrieve', + }, + }, + + request: { + url: (params) => buildRampUrl(`/bills/${encodeURIComponent(params.billId.trim())}`), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp bill'), + output: {}, + } + } + + return { + success: true, + output: { + bill: data, + }, + } + }, + + outputs: { + bill: { + type: 'object', + description: 'The requested Ramp bill', + properties: { + id: { type: 'string', description: 'Unique identifier for the bill' }, + invoice_number: { type: 'string', description: 'Invoice number of the bill' }, + amount: { + type: 'object', + description: + 'Canonical bill amount (integer amount in the smallest currency denomination plus currency code)', + }, + status: { type: 'string', description: 'Status of the bill (e.g. OPEN, PAID)' }, + due_at: { type: 'string', description: 'When the bill is due' }, + issued_at: { type: 'string', description: 'When the bill was issued' }, + vendor: { type: 'object', description: 'Vendor the bill is payable to' }, + memo: { type: 'string', description: 'Memo attached to the bill' }, + line_items: { type: 'array', description: 'Line items on the bill' }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/get_business.ts b/apps/sim/tools/ramp/get_business.ts new file mode 100644 index 0000000000..85b56de705 --- /dev/null +++ b/apps/sim/tools/ramp/get_business.ts @@ -0,0 +1,69 @@ +import type { RampGetBusinessParams, RampGetBusinessResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetBusinessTool: ToolConfig = { + id: 'ramp_get_business', + name: 'Ramp Get Business', + description: 'Retrieve information about the authorized Ramp business', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + }, + + request: { + url: () => buildRampUrl('/business'), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp business'), + output: {}, + } + } + + return { + success: true, + output: { + business: data, + }, + } + }, + + outputs: { + business: { + type: 'object', + description: 'The authorized Ramp business', + properties: { + id: { type: 'string', description: 'Unique identifier for the business' }, + business_name_legal: { type: 'string', description: 'Legal name of the business' }, + business_name_on_card: { type: 'string', description: 'Business name shown on cards' }, + active: { type: 'boolean', description: 'Whether the business account is active' }, + created_time: { type: 'string', description: 'When the business account was created' }, + is_reimbursements_enabled: { + type: 'boolean', + description: 'Whether reimbursements are enabled for the business', + }, + website: { type: 'string', description: 'Company website URL' }, + phone: { type: 'string', description: 'Primary contact phone number' }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/get_business_balance.ts b/apps/sim/tools/ramp/get_business_balance.ts new file mode 100644 index 0000000000..050cf7e26b --- /dev/null +++ b/apps/sim/tools/ramp/get_business_balance.ts @@ -0,0 +1,91 @@ +import type { + RampGetBusinessBalanceParams, + RampGetBusinessBalanceResponse, +} from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetBusinessBalanceTool: ToolConfig< + RampGetBusinessBalanceParams, + RampGetBusinessBalanceResponse +> = { + id: 'ramp_get_business_balance', + name: 'Ramp Get Business Balance', + description: 'Retrieve the current balance and limits of the authorized Ramp business', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + }, + + request: { + url: () => buildRampUrl('/business/balance'), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp business balance'), + output: {}, + } + } + + return { + success: true, + output: { + balance: data, + }, + } + }, + + outputs: { + balance: { + type: 'object', + description: 'Balance and limit details for the Ramp business', + properties: { + balance_including_pending: { + type: 'number', + description: 'Total balance including pending transactions, in U.S. dollars', + }, + card_balance_including_pending: { + type: 'number', + description: 'Card balance including pending transactions, in U.S. dollars', + }, + card_balance_excluding_pending: { + type: 'number', + description: 'Card balance excluding pending transactions, in U.S. dollars', + }, + card_limit: { type: 'number', description: 'Total card limit in U.S. dollars' }, + available_card_limit: { + type: 'number', + description: 'Remaining available card limit in U.S. dollars', + }, + card_limit_amount: { + type: 'object', + description: + 'Canonical card limit (integer amount in the smallest currency denomination plus currency code)', + }, + available_card_limit_amount: { + type: 'object', + description: + 'Canonical available card limit (integer amount in the smallest currency denomination plus currency code)', + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/get_card.ts b/apps/sim/tools/ramp/get_card.ts new file mode 100644 index 0000000000..96b85b4052 --- /dev/null +++ b/apps/sim/tools/ramp/get_card.ts @@ -0,0 +1,72 @@ +import type { RampGetCardParams, RampGetCardResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetCardTool: ToolConfig = { + id: 'ramp_get_card', + name: 'Ramp Get Card', + description: 'Retrieve a single Ramp corporate card by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + cardId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the card to retrieve', + }, + }, + + request: { + url: (params) => buildRampUrl(`/cards/${encodeURIComponent(params.cardId.trim())}`), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp card'), + output: {}, + } + } + + return { + success: true, + output: { + card: data, + }, + } + }, + + outputs: { + card: { + type: 'object', + description: 'The requested Ramp card', + properties: { + id: { type: 'string', description: 'Unique identifier for the card' }, + display_name: { type: 'string', description: 'Display name of the card' }, + last_four: { type: 'string', description: 'Last four digits of the card number' }, + cardholder_id: { type: 'string', description: 'User ID of the cardholder' }, + cardholder_name: { type: 'string', description: 'Full name of the cardholder' }, + is_physical: { type: 'boolean', description: 'Whether the card is physical' }, + state: { type: 'string', description: 'State of the card (e.g. ACTIVE)' }, + expiration: { type: 'string', description: 'Expiration date of the card' }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/get_department.ts b/apps/sim/tools/ramp/get_department.ts new file mode 100644 index 0000000000..d80f2645da --- /dev/null +++ b/apps/sim/tools/ramp/get_department.ts @@ -0,0 +1,68 @@ +import type { RampGetDepartmentParams, RampGetDepartmentResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetDepartmentTool: ToolConfig = + { + id: 'ramp_get_department', + name: 'Ramp Get Department', + description: 'Retrieve a single Ramp department by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + departmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the department to retrieve', + }, + }, + + request: { + url: (params) => + buildRampUrl(`/departments/${encodeURIComponent(params.departmentId.trim())}`), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp department'), + output: {}, + } + } + + return { + success: true, + output: { + department: data, + }, + } + }, + + outputs: { + department: { + type: 'object', + description: 'The requested Ramp department', + properties: { + id: { type: 'string', description: 'Unique identifier for the department' }, + name: { type: 'string', description: 'Name of the department' }, + }, + }, + }, + } diff --git a/apps/sim/tools/ramp/get_limit.ts b/apps/sim/tools/ramp/get_limit.ts new file mode 100644 index 0000000000..4a66867cf7 --- /dev/null +++ b/apps/sim/tools/ramp/get_limit.ts @@ -0,0 +1,75 @@ +import type { RampGetLimitParams, RampGetLimitResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetLimitTool: ToolConfig = { + id: 'ramp_get_limit', + name: 'Ramp Get Limit', + description: 'Retrieve a single Ramp spend limit by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + limitId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the spend limit to retrieve', + }, + }, + + request: { + url: (params) => buildRampUrl(`/limits/${encodeURIComponent(params.limitId.trim())}`), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp limit'), + output: {}, + } + } + + return { + success: true, + output: { + limit: data, + }, + } + }, + + outputs: { + limit: { + type: 'object', + description: 'The requested Ramp spend limit', + properties: { + id: { type: 'string', description: 'Unique identifier for the spend limit' }, + display_name: { type: 'string', description: 'Display name of the spend limit' }, + state: { type: 'string', description: 'State of the spend limit (e.g. ACTIVE)' }, + balance: { + type: 'object', + description: + 'Balance of the spend limit (cleared, pending, and total canonical amounts in the smallest currency denomination)', + }, + users: { type: 'array', description: 'Users the spend limit applies to' }, + cards: { type: 'array', description: 'Cards attached to the spend limit' }, + spend_program_id: { type: 'string', description: 'ID of the associated spend program' }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/get_receipt.ts b/apps/sim/tools/ramp/get_receipt.ts new file mode 100644 index 0000000000..03d8caed2c --- /dev/null +++ b/apps/sim/tools/ramp/get_receipt.ts @@ -0,0 +1,72 @@ +import type { RampGetReceiptParams, RampGetReceiptResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetReceiptTool: ToolConfig = { + id: 'ramp_get_receipt', + name: 'Ramp Get Receipt', + description: 'Retrieve a single Ramp receipt by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + receiptId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the receipt to retrieve', + }, + }, + + request: { + url: (params) => buildRampUrl(`/receipts/${encodeURIComponent(params.receiptId.trim())}`), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp receipt'), + output: {}, + } + } + + return { + success: true, + output: { + receipt: data, + }, + } + }, + + outputs: { + receipt: { + type: 'object', + description: 'The requested Ramp receipt', + properties: { + id: { type: 'string', description: 'Unique identifier for the receipt' }, + receipt_url: { + type: 'string', + description: 'Pre-signed URL to download the receipt image (valid for one hour)', + }, + transaction_id: { type: 'string', description: 'Transaction the receipt is attached to' }, + user_id: { type: 'string', description: 'User who uploaded the receipt' }, + created_at: { type: 'string', description: 'When the receipt was created' }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/get_reimbursement.ts b/apps/sim/tools/ramp/get_reimbursement.ts new file mode 100644 index 0000000000..6c88b2f4cf --- /dev/null +++ b/apps/sim/tools/ramp/get_reimbursement.ts @@ -0,0 +1,80 @@ +import type { RampGetReimbursementParams, RampGetReimbursementResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetReimbursementTool: ToolConfig< + RampGetReimbursementParams, + RampGetReimbursementResponse +> = { + id: 'ramp_get_reimbursement', + name: 'Ramp Get Reimbursement', + description: 'Retrieve a single Ramp reimbursement by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + reimbursementId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the reimbursement to retrieve', + }, + }, + + request: { + url: (params) => + buildRampUrl(`/reimbursements/${encodeURIComponent(params.reimbursementId.trim())}`), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp reimbursement'), + output: {}, + } + } + + return { + success: true, + output: { + reimbursement: data, + }, + } + }, + + outputs: { + reimbursement: { + type: 'object', + description: 'The requested Ramp reimbursement', + properties: { + id: { type: 'string', description: 'Unique identifier for the reimbursement' }, + amount: { type: 'number', description: 'Reimbursement amount' }, + currency: { type: 'string', description: 'ISO 4217 currency code' }, + merchant: { type: 'string', description: 'Merchant the expense was made at' }, + memo: { type: 'string', description: 'Memo attached to the reimbursement' }, + state: { type: 'string', description: 'State of the reimbursement (e.g. APPROVED)' }, + type: { type: 'string', description: 'Type of reimbursement' }, + user_id: { type: 'string', description: 'ID of the user being reimbursed' }, + user_full_name: { type: 'string', description: 'Full name of the user' }, + created_at: { type: 'string', description: 'When the reimbursement was created' }, + transaction_date: { type: 'string', description: 'Date of the underlying expense' }, + receipts: { type: 'array', description: 'IDs of receipts attached to the reimbursement' }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/get_spend_program.ts b/apps/sim/tools/ramp/get_spend_program.ts new file mode 100644 index 0000000000..63eeff5eea --- /dev/null +++ b/apps/sim/tools/ramp/get_spend_program.ts @@ -0,0 +1,80 @@ +import type { RampGetSpendProgramParams, RampGetSpendProgramResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetSpendProgramTool: ToolConfig< + RampGetSpendProgramParams, + RampGetSpendProgramResponse +> = { + id: 'ramp_get_spend_program', + name: 'Ramp Get Spend Program', + description: 'Retrieve a single Ramp spend program by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + spendProgramId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the spend program to retrieve', + }, + }, + + request: { + url: (params) => + buildRampUrl(`/spend-programs/${encodeURIComponent(params.spendProgramId.trim())}`), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp spend program'), + output: {}, + } + } + + return { + success: true, + output: { + spendProgram: data, + }, + } + }, + + outputs: { + spendProgram: { + type: 'object', + description: 'The requested Ramp spend program', + properties: { + id: { type: 'string', description: 'Unique identifier for the spend program' }, + display_name: { type: 'string', description: 'Display name of the spend program' }, + description: { type: 'string', description: 'Description of the spend program' }, + is_shareable: { + type: 'boolean', + description: 'Whether limits under this program can be shared', + }, + permitted_spend_types: { + type: 'object', + description: 'Spend types permitted by the program (card and/or reimbursement)', + }, + restrictions: { type: 'object', description: 'Spending restrictions of the program' }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/get_transaction.ts b/apps/sim/tools/ramp/get_transaction.ts new file mode 100644 index 0000000000..6e69647713 --- /dev/null +++ b/apps/sim/tools/ramp/get_transaction.ts @@ -0,0 +1,78 @@ +import type { RampGetTransactionParams, RampGetTransactionResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetTransactionTool: ToolConfig< + RampGetTransactionParams, + RampGetTransactionResponse +> = { + id: 'ramp_get_transaction', + name: 'Ramp Get Transaction', + description: 'Retrieve a single Ramp card transaction by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + transactionId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the transaction to retrieve', + }, + }, + + request: { + url: (params) => + buildRampUrl(`/transactions/${encodeURIComponent(params.transactionId.trim())}`), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp transaction'), + output: {}, + } + } + + return { + success: true, + output: { + transaction: data, + }, + } + }, + + outputs: { + transaction: { + type: 'object', + description: 'The requested Ramp transaction', + properties: { + id: { type: 'string', description: 'Unique identifier for the transaction' }, + amount: { type: 'number', description: 'Settled amount in U.S. dollars' }, + currency_code: { type: 'string', description: 'ISO 4217 currency code' }, + merchant_name: { type: 'string', description: 'Name of the merchant' }, + memo: { type: 'string', description: 'Memo attached to the transaction' }, + state: { type: 'string', description: 'Transaction state (e.g. CLEARED, PENDING)' }, + user_transaction_time: { type: 'string', description: 'When the transaction occurred' }, + card_id: { type: 'string', description: 'ID of the card used' }, + card_holder: { type: 'object', description: 'Cardholder details (user and department)' }, + receipts: { type: 'array', description: 'IDs of receipts attached to the transaction' }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/get_user.ts b/apps/sim/tools/ramp/get_user.ts new file mode 100644 index 0000000000..b6b037233f --- /dev/null +++ b/apps/sim/tools/ramp/get_user.ts @@ -0,0 +1,72 @@ +import type { RampGetUserParams, RampGetUserResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetUserTool: ToolConfig = { + id: 'ramp_get_user', + name: 'Ramp Get User', + description: 'Retrieve a single Ramp user by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + userId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the user to retrieve', + }, + }, + + request: { + url: (params) => buildRampUrl(`/users/${encodeURIComponent(params.userId.trim())}`), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp user'), + output: {}, + } + } + + return { + success: true, + output: { + user: data, + }, + } + }, + + outputs: { + user: { + type: 'object', + description: 'The requested Ramp user', + properties: { + id: { type: 'string', description: 'Unique identifier for the user' }, + first_name: { type: 'string', description: 'First name of the user' }, + last_name: { type: 'string', description: 'Last name of the user' }, + email: { type: 'string', description: 'Email address of the user' }, + role: { type: 'string', description: 'Role of the user in the business' }, + status: { type: 'string', description: 'Status of the user (e.g. ACTIVE)' }, + department_id: { type: 'string', description: 'ID of the user department' }, + manager_id: { type: 'string', description: 'ID of the user manager' }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/get_vendor.ts b/apps/sim/tools/ramp/get_vendor.ts new file mode 100644 index 0000000000..2c6d931dd0 --- /dev/null +++ b/apps/sim/tools/ramp/get_vendor.ts @@ -0,0 +1,79 @@ +import type { RampGetVendorParams, RampGetVendorResponse } from '@/tools/ramp/types' +import { buildRampHeaders, buildRampUrl, extractRampError } from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampGetVendorTool: ToolConfig = { + id: 'ramp_get_vendor', + name: 'Ramp Get Vendor', + description: 'Retrieve a single Ramp vendor by ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + vendorId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the vendor to retrieve', + }, + }, + + request: { + url: (params) => buildRampUrl(`/vendors/${encodeURIComponent(params.vendorId.trim())}`), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to get Ramp vendor'), + output: {}, + } + } + + return { + success: true, + output: { + vendor: data, + }, + } + }, + + outputs: { + vendor: { + type: 'object', + description: 'The requested Ramp vendor', + properties: { + id: { type: 'string', description: 'Unique identifier for the vendor' }, + name: { type: 'string', description: 'Name of the vendor' }, + state: { type: 'string', description: 'State of the vendor record' }, + is_active: { type: 'boolean', description: 'Whether the vendor is active' }, + country: { type: 'string', description: 'Country of the vendor' }, + total_spend_ytd: { + type: 'object', + description: + 'Year-to-date spend with the vendor (integer amount in the smallest currency denomination plus currency code)', + }, + total_spend_all_time: { + type: 'object', + description: + 'All-time spend with the vendor (integer amount in the smallest currency denomination plus currency code)', + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ramp/index.ts b/apps/sim/tools/ramp/index.ts new file mode 100644 index 0000000000..e503ee3c74 --- /dev/null +++ b/apps/sim/tools/ramp/index.ts @@ -0,0 +1,53 @@ +import { rampCreateDepartmentTool } from '@/tools/ramp/create_department' +import { rampGetBillTool } from '@/tools/ramp/get_bill' +import { rampGetBusinessTool } from '@/tools/ramp/get_business' +import { rampGetBusinessBalanceTool } from '@/tools/ramp/get_business_balance' +import { rampGetCardTool } from '@/tools/ramp/get_card' +import { rampGetDepartmentTool } from '@/tools/ramp/get_department' +import { rampGetLimitTool } from '@/tools/ramp/get_limit' +import { rampGetReceiptTool } from '@/tools/ramp/get_receipt' +import { rampGetReimbursementTool } from '@/tools/ramp/get_reimbursement' +import { rampGetSpendProgramTool } from '@/tools/ramp/get_spend_program' +import { rampGetTransactionTool } from '@/tools/ramp/get_transaction' +import { rampGetUserTool } from '@/tools/ramp/get_user' +import { rampGetVendorTool } from '@/tools/ramp/get_vendor' +import { rampListBillsTool } from '@/tools/ramp/list_bills' +import { rampListCardsTool } from '@/tools/ramp/list_cards' +import { rampListDepartmentsTool } from '@/tools/ramp/list_departments' +import { rampListEntitiesTool } from '@/tools/ramp/list_entities' +import { rampListLimitsTool } from '@/tools/ramp/list_limits' +import { rampListReceiptsTool } from '@/tools/ramp/list_receipts' +import { rampListReimbursementsTool } from '@/tools/ramp/list_reimbursements' +import { rampListSpendProgramsTool } from '@/tools/ramp/list_spend_programs' +import { rampListTransactionsTool } from '@/tools/ramp/list_transactions' +import { rampListUsersTool } from '@/tools/ramp/list_users' +import { rampListVendorsTool } from '@/tools/ramp/list_vendors' +import { rampUploadReceiptTool } from '@/tools/ramp/upload_receipt' + +export { + rampCreateDepartmentTool, + rampGetBillTool, + rampGetBusinessTool, + rampGetBusinessBalanceTool, + rampGetCardTool, + rampGetDepartmentTool, + rampGetLimitTool, + rampGetReceiptTool, + rampGetReimbursementTool, + rampGetSpendProgramTool, + rampGetTransactionTool, + rampGetUserTool, + rampGetVendorTool, + rampListBillsTool, + rampListCardsTool, + rampListDepartmentsTool, + rampListEntitiesTool, + rampListLimitsTool, + rampListReceiptsTool, + rampListReimbursementsTool, + rampListSpendProgramsTool, + rampListTransactionsTool, + rampListUsersTool, + rampListVendorsTool, + rampUploadReceiptTool, +} diff --git a/apps/sim/tools/ramp/list_bills.ts b/apps/sim/tools/ramp/list_bills.ts new file mode 100644 index 0000000000..1a47428f03 --- /dev/null +++ b/apps/sim/tools/ramp/list_bills.ts @@ -0,0 +1,107 @@ +import type { RampListBillsParams, RampListBillsResponse } from '@/tools/ramp/types' +import { + buildRampHeaders, + buildRampUrl, + extractNextStart, + extractRampError, +} from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampListBillsTool: ToolConfig = { + id: 'ramp_list_bills', + name: 'Ramp List Bills', + description: 'List bills in Ramp with optional filters', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + vendorId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter bills by vendor ID', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (between 2 and 100, default 20)', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor: the ID of the last entity from the previous page', + }, + }, + + request: { + url: (params) => + buildRampUrl('/bills', { + vendor_id: params.vendorId, + page_size: params.pageSize, + start: params.start, + }), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to list Ramp bills'), + output: {}, + } + } + + return { + success: true, + output: { + bills: data.data ?? [], + nextStart: extractNextStart(data.page?.next), + }, + } + }, + + outputs: { + bills: { + type: 'array', + description: 'List of Ramp bills', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for the bill' }, + invoice_number: { type: 'string', description: 'Invoice number of the bill' }, + amount: { + type: 'object', + description: + 'Canonical bill amount (integer amount in the smallest currency denomination plus currency code)', + }, + status: { type: 'string', description: 'Status of the bill (e.g. OPEN, PAID)' }, + due_at: { type: 'string', description: 'When the bill is due' }, + issued_at: { type: 'string', description: 'When the bill was issued' }, + vendor: { type: 'object', description: 'Vendor the bill is payable to' }, + memo: { type: 'string', description: 'Memo attached to the bill' }, + }, + }, + }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ramp/list_cards.ts b/apps/sim/tools/ramp/list_cards.ts new file mode 100644 index 0000000000..3cdd85e903 --- /dev/null +++ b/apps/sim/tools/ramp/list_cards.ts @@ -0,0 +1,110 @@ +import type { RampListCardsParams, RampListCardsResponse } from '@/tools/ramp/types' +import { + buildRampHeaders, + buildRampUrl, + extractNextStart, + extractRampError, +} from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampListCardsTool: ToolConfig = { + id: 'ramp_list_cards', + name: 'Ramp List Cards', + description: 'List corporate cards in Ramp with optional filters', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + userId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter cards by cardholder user ID', + }, + displayName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter cards by display name', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (between 2 and 100, default 20)', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor: the ID of the last entity from the previous page', + }, + }, + + request: { + url: (params) => + buildRampUrl('/cards', { + user_id: params.userId, + display_name: params.displayName, + page_size: params.pageSize, + start: params.start, + }), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to list Ramp cards'), + output: {}, + } + } + + return { + success: true, + output: { + cards: data.data ?? [], + nextStart: extractNextStart(data.page?.next), + }, + } + }, + + outputs: { + cards: { + type: 'array', + description: 'List of Ramp corporate cards', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for the card' }, + display_name: { type: 'string', description: 'Display name of the card' }, + last_four: { type: 'string', description: 'Last four digits of the card number' }, + cardholder_id: { type: 'string', description: 'User ID of the cardholder' }, + cardholder_name: { type: 'string', description: 'Full name of the cardholder' }, + is_physical: { type: 'boolean', description: 'Whether the card is physical' }, + state: { type: 'string', description: 'State of the card (e.g. ACTIVE)' }, + expiration: { type: 'string', description: 'Expiration date of the card' }, + }, + }, + }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ramp/list_departments.ts b/apps/sim/tools/ramp/list_departments.ts new file mode 100644 index 0000000000..566bed0503 --- /dev/null +++ b/apps/sim/tools/ramp/list_departments.ts @@ -0,0 +1,93 @@ +import type { RampListDepartmentsParams, RampListDepartmentsResponse } from '@/tools/ramp/types' +import { + buildRampHeaders, + buildRampUrl, + extractNextStart, + extractRampError, +} from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampListDepartmentsTool: ToolConfig< + RampListDepartmentsParams, + RampListDepartmentsResponse +> = { + id: 'ramp_list_departments', + name: 'Ramp List Departments', + description: 'List departments in the Ramp business', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (between 2 and 100, default 20)', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor: the ID of the last entity from the previous page', + }, + }, + + request: { + url: (params) => + buildRampUrl('/departments', { + page_size: params.pageSize, + start: params.start, + }), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to list Ramp departments'), + output: {}, + } + } + + return { + success: true, + output: { + departments: data.data ?? [], + nextStart: extractNextStart(data.page?.next), + }, + } + }, + + outputs: { + departments: { + type: 'array', + description: 'List of departments in the Ramp business', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for the department' }, + name: { type: 'string', description: 'Name of the department' }, + }, + }, + }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ramp/list_entities.ts b/apps/sim/tools/ramp/list_entities.ts new file mode 100644 index 0000000000..698c974554 --- /dev/null +++ b/apps/sim/tools/ramp/list_entities.ts @@ -0,0 +1,102 @@ +import type { RampListEntitiesParams, RampListEntitiesResponse } from '@/tools/ramp/types' +import { + buildRampHeaders, + buildRampUrl, + extractNextStart, + extractRampError, +} from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampListEntitiesTool: ToolConfig = { + id: 'ramp_list_entities', + name: 'Ramp List Entities', + description: 'List business entities in Ramp with optional filters', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + entityName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter entities by name', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (between 2 and 100, default 20)', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor: the ID of the last entity from the previous page', + }, + }, + + request: { + url: (params) => + buildRampUrl('/entities', { + entity_name: params.entityName, + page_size: params.pageSize, + start: params.start, + }), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to list Ramp entities'), + output: {}, + } + } + + return { + success: true, + output: { + entities: data.data ?? [], + nextStart: extractNextStart(data.page?.next), + }, + } + }, + + outputs: { + entities: { + type: 'array', + description: 'List of business entities in Ramp', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for the entity' }, + entity_name: { type: 'string', description: 'Name of the entity' }, + currency: { type: 'string', description: 'Primary currency of the entity' }, + is_primary: { + type: 'boolean', + description: 'Whether this is the primary entity of the business', + }, + }, + }, + }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ramp/list_limits.ts b/apps/sim/tools/ramp/list_limits.ts new file mode 100644 index 0000000000..9f4b3273a2 --- /dev/null +++ b/apps/sim/tools/ramp/list_limits.ts @@ -0,0 +1,113 @@ +import type { RampListLimitsParams, RampListLimitsResponse } from '@/tools/ramp/types' +import { + buildRampHeaders, + buildRampUrl, + extractNextStart, + extractRampError, +} from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampListLimitsTool: ToolConfig = { + id: 'ramp_list_limits', + name: 'Ramp List Limits', + description: 'List spend limits in Ramp with optional filters', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + userId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter limits by user ID', + }, + cardId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter limits by card ID', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (between 2 and 100, default 20)', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor: the ID of the last entity from the previous page', + }, + }, + + request: { + url: (params) => + buildRampUrl('/limits', { + user_id: params.userId, + card_id: params.cardId, + page_size: params.pageSize, + start: params.start, + }), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to list Ramp limits'), + output: {}, + } + } + + return { + success: true, + output: { + limits: data.data ?? [], + nextStart: extractNextStart(data.page?.next), + }, + } + }, + + outputs: { + limits: { + type: 'array', + description: 'List of Ramp spend limits', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for the spend limit' }, + display_name: { type: 'string', description: 'Display name of the spend limit' }, + state: { type: 'string', description: 'State of the spend limit (e.g. ACTIVE)' }, + balance: { + type: 'object', + description: + 'Balance of the spend limit (cleared, pending, and total canonical amounts in the smallest currency denomination)', + }, + users: { type: 'array', description: 'Users the spend limit applies to' }, + cards: { type: 'array', description: 'Cards attached to the spend limit' }, + spend_program_id: { type: 'string', description: 'ID of the associated spend program' }, + }, + }, + }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ramp/list_receipts.ts b/apps/sim/tools/ramp/list_receipts.ts new file mode 100644 index 0000000000..68c2ca6526 --- /dev/null +++ b/apps/sim/tools/ramp/list_receipts.ts @@ -0,0 +1,119 @@ +import type { RampListReceiptsParams, RampListReceiptsResponse } from '@/tools/ramp/types' +import { + buildRampHeaders, + buildRampUrl, + extractNextStart, + extractRampError, +} from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampListReceiptsTool: ToolConfig = { + id: 'ramp_list_receipts', + name: 'Ramp List Receipts', + description: 'List receipts in Ramp with optional filters', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + transactionId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter receipts by transaction ID', + }, + fromDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Only include receipts for transactions that occurred after this ISO 8601 timestamp', + }, + toDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Only include receipts for transactions that occurred before this ISO 8601 timestamp', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (between 2 and 100, default 20)', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor: the ID of the last entity from the previous page', + }, + }, + + request: { + url: (params) => + buildRampUrl('/receipts', { + transaction_id: params.transactionId, + from_date: params.fromDate, + to_date: params.toDate, + page_size: params.pageSize, + start: params.start, + }), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to list Ramp receipts'), + output: {}, + } + } + + return { + success: true, + output: { + receipts: data.data ?? [], + nextStart: extractNextStart(data.page?.next), + }, + } + }, + + outputs: { + receipts: { + type: 'array', + description: 'List of Ramp receipts', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for the receipt' }, + receipt_url: { + type: 'string', + description: 'Pre-signed URL to download the receipt image (valid for one hour)', + }, + transaction_id: { type: 'string', description: 'Transaction the receipt is attached to' }, + user_id: { type: 'string', description: 'User who uploaded the receipt' }, + created_at: { type: 'string', description: 'When the receipt was created' }, + }, + }, + }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ramp/list_reimbursements.ts b/apps/sim/tools/ramp/list_reimbursements.ts new file mode 100644 index 0000000000..f33c515c69 --- /dev/null +++ b/apps/sim/tools/ramp/list_reimbursements.ts @@ -0,0 +1,126 @@ +import type { + RampListReimbursementsParams, + RampListReimbursementsResponse, +} from '@/tools/ramp/types' +import { + buildRampHeaders, + buildRampUrl, + extractNextStart, + extractRampError, +} from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampListReimbursementsTool: ToolConfig< + RampListReimbursementsParams, + RampListReimbursementsResponse +> = { + id: 'ramp_list_reimbursements', + name: 'Ramp List Reimbursements', + description: 'List reimbursements in Ramp with optional filters', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + userId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter reimbursements by user ID', + }, + fromDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include reimbursements created after this ISO 8601 timestamp', + }, + toDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include reimbursements created before this ISO 8601 timestamp', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (between 2 and 100, default 20)', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor: the ID of the last entity from the previous page', + }, + }, + + request: { + url: (params) => + buildRampUrl('/reimbursements', { + user_id: params.userId, + from_date: params.fromDate, + to_date: params.toDate, + page_size: params.pageSize, + start: params.start, + }), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to list Ramp reimbursements'), + output: {}, + } + } + + return { + success: true, + output: { + reimbursements: data.data ?? [], + nextStart: extractNextStart(data.page?.next), + }, + } + }, + + outputs: { + reimbursements: { + type: 'array', + description: 'List of Ramp reimbursements', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for the reimbursement' }, + amount: { type: 'number', description: 'Reimbursement amount' }, + currency: { type: 'string', description: 'ISO 4217 currency code' }, + merchant: { type: 'string', description: 'Merchant the expense was made at' }, + memo: { type: 'string', description: 'Memo attached to the reimbursement' }, + state: { type: 'string', description: 'State of the reimbursement (e.g. APPROVED)' }, + type: { type: 'string', description: 'Type of reimbursement' }, + user_id: { type: 'string', description: 'ID of the user being reimbursed' }, + user_full_name: { type: 'string', description: 'Full name of the user' }, + created_at: { type: 'string', description: 'When the reimbursement was created' }, + transaction_date: { type: 'string', description: 'Date of the underlying expense' }, + }, + }, + }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ramp/list_spend_programs.ts b/apps/sim/tools/ramp/list_spend_programs.ts new file mode 100644 index 0000000000..11d760b00d --- /dev/null +++ b/apps/sim/tools/ramp/list_spend_programs.ts @@ -0,0 +1,103 @@ +import type { RampListSpendProgramsParams, RampListSpendProgramsResponse } from '@/tools/ramp/types' +import { + buildRampHeaders, + buildRampUrl, + extractNextStart, + extractRampError, +} from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampListSpendProgramsTool: ToolConfig< + RampListSpendProgramsParams, + RampListSpendProgramsResponse +> = { + id: 'ramp_list_spend_programs', + name: 'Ramp List Spend Programs', + description: 'List spend programs in the Ramp business', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (between 2 and 100, default 20)', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor: the ID of the last entity from the previous page', + }, + }, + + request: { + url: (params) => + buildRampUrl('/spend-programs', { + page_size: params.pageSize, + start: params.start, + }), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to list Ramp spend programs'), + output: {}, + } + } + + return { + success: true, + output: { + spendPrograms: data.data ?? [], + nextStart: extractNextStart(data.page?.next), + }, + } + }, + + outputs: { + spendPrograms: { + type: 'array', + description: 'List of spend programs in the Ramp business', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for the spend program' }, + display_name: { type: 'string', description: 'Display name of the spend program' }, + description: { type: 'string', description: 'Description of the spend program' }, + is_shareable: { + type: 'boolean', + description: 'Whether limits under this program can be shared', + }, + permitted_spend_types: { + type: 'object', + description: 'Spend types permitted by the program (card and/or reimbursement)', + }, + restrictions: { type: 'object', description: 'Spending restrictions of the program' }, + }, + }, + }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ramp/list_transactions.ts b/apps/sim/tools/ramp/list_transactions.ts new file mode 100644 index 0000000000..daec6d3af8 --- /dev/null +++ b/apps/sim/tools/ramp/list_transactions.ts @@ -0,0 +1,165 @@ +import type { RampListTransactionsParams, RampListTransactionsResponse } from '@/tools/ramp/types' +import { + buildRampHeaders, + buildRampUrl, + extractNextStart, + extractRampError, +} from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampListTransactionsTool: ToolConfig< + RampListTransactionsParams, + RampListTransactionsResponse +> = { + id: 'ramp_list_transactions', + name: 'Ramp List Transactions', + description: 'List card transactions in Ramp with optional filters', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + userId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter transactions by user ID', + }, + cardId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter transactions by card ID', + }, + departmentId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter transactions by department ID', + }, + merchantId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter transactions by merchant ID', + }, + state: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter by transaction state: ALL, CLEARED, COMPLETION, DECLINED, ERROR, PENDING, or PENDING_INITIATION. Declined transactions are only included when set to ALL or DECLINED.', + }, + minAmount: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include transactions larger than this U.S. dollar amount', + }, + maxAmount: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include transactions smaller than this U.S. dollar amount', + }, + fromDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include transactions that occurred after this ISO 8601 timestamp', + }, + toDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include transactions that occurred before this ISO 8601 timestamp', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (between 2 and 100, default 20)', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor: the ID of the last entity from the previous page', + }, + }, + + request: { + url: (params) => + buildRampUrl('/transactions', { + user_id: params.userId, + card_id: params.cardId, + department_id: params.departmentId, + merchant_id: params.merchantId, + state: params.state, + min_amount: params.minAmount, + max_amount: params.maxAmount, + from_date: params.fromDate, + to_date: params.toDate, + page_size: params.pageSize, + start: params.start, + }), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to list Ramp transactions'), + output: {}, + } + } + + return { + success: true, + output: { + transactions: data.data ?? [], + nextStart: extractNextStart(data.page?.next), + }, + } + }, + + outputs: { + transactions: { + type: 'array', + description: 'List of Ramp card transactions', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for the transaction' }, + amount: { type: 'number', description: 'Settled amount in U.S. dollars' }, + currency_code: { type: 'string', description: 'ISO 4217 currency code' }, + merchant_name: { type: 'string', description: 'Name of the merchant' }, + memo: { type: 'string', description: 'Memo attached to the transaction' }, + state: { type: 'string', description: 'Transaction state (e.g. CLEARED, PENDING)' }, + user_transaction_time: { type: 'string', description: 'When the transaction occurred' }, + card_id: { type: 'string', description: 'ID of the card used' }, + card_holder: { type: 'object', description: 'Cardholder details (user and department)' }, + receipts: { type: 'array', description: 'IDs of receipts attached to the transaction' }, + }, + }, + }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ramp/list_users.ts b/apps/sim/tools/ramp/list_users.ts new file mode 100644 index 0000000000..d96d67515a --- /dev/null +++ b/apps/sim/tools/ramp/list_users.ts @@ -0,0 +1,110 @@ +import type { RampListUsersParams, RampListUsersResponse } from '@/tools/ramp/types' +import { + buildRampHeaders, + buildRampUrl, + extractNextStart, + extractRampError, +} from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampListUsersTool: ToolConfig = { + id: 'ramp_list_users', + name: 'Ramp List Users', + description: 'List users in the Ramp business with optional filters', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + email: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter users by email address', + }, + departmentId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter users by department ID', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (between 2 and 100, default 20)', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor: the ID of the last entity from the previous page', + }, + }, + + request: { + url: (params) => + buildRampUrl('/users', { + email: params.email, + department_id: params.departmentId, + page_size: params.pageSize, + start: params.start, + }), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to list Ramp users'), + output: {}, + } + } + + return { + success: true, + output: { + users: data.data ?? [], + nextStart: extractNextStart(data.page?.next), + }, + } + }, + + outputs: { + users: { + type: 'array', + description: 'List of users in the Ramp business', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for the user' }, + first_name: { type: 'string', description: 'First name of the user' }, + last_name: { type: 'string', description: 'Last name of the user' }, + email: { type: 'string', description: 'Email address of the user' }, + role: { type: 'string', description: 'Role of the user in the business' }, + status: { type: 'string', description: 'Status of the user (e.g. ACTIVE)' }, + department_id: { type: 'string', description: 'ID of the user department' }, + manager_id: { type: 'string', description: 'ID of the user manager' }, + }, + }, + }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ramp/list_vendors.ts b/apps/sim/tools/ramp/list_vendors.ts new file mode 100644 index 0000000000..7e4b9baab6 --- /dev/null +++ b/apps/sim/tools/ramp/list_vendors.ts @@ -0,0 +1,105 @@ +import type { RampListVendorsParams, RampListVendorsResponse } from '@/tools/ramp/types' +import { + buildRampHeaders, + buildRampUrl, + extractNextStart, + extractRampError, +} from '@/tools/ramp/utils' +import type { ToolConfig } from '@/tools/types' + +export const rampListVendorsTool: ToolConfig = { + id: 'ramp_list_vendors', + name: 'Ramp List Vendors', + description: 'List vendors in Ramp with optional filters', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + vendorName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter vendors by name', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (between 2 and 100, default 20)', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination cursor: the ID of the last entity from the previous page', + }, + }, + + request: { + url: (params) => + buildRampUrl('/vendors', { + name: params.vendorName, + page_size: params.pageSize, + start: params.start, + }), + method: 'GET', + headers: (params) => buildRampHeaders(params), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: extractRampError(data, 'Failed to list Ramp vendors'), + output: {}, + } + } + + return { + success: true, + output: { + vendors: data.data ?? [], + nextStart: extractNextStart(data.page?.next), + }, + } + }, + + outputs: { + vendors: { + type: 'array', + description: 'List of Ramp vendors', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for the vendor' }, + name: { type: 'string', description: 'Name of the vendor' }, + state: { type: 'string', description: 'State of the vendor record' }, + is_active: { type: 'boolean', description: 'Whether the vendor is active' }, + country: { type: 'string', description: 'Country of the vendor' }, + total_spend_ytd: { + type: 'object', + description: + 'Year-to-date spend with the vendor (integer amount in the smallest currency denomination plus currency code)', + }, + }, + }, + }, + nextStart: { + type: 'string', + description: 'Cursor for the next page of results (null when there are no more pages)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/ramp/types.ts b/apps/sim/tools/ramp/types.ts new file mode 100644 index 0000000000..5ab81aa576 --- /dev/null +++ b/apps/sim/tools/ramp/types.ts @@ -0,0 +1,535 @@ +import type { UserFileLike } from '@/lib/core/utils/user-file' +import type { ToolResponse } from '@/tools/types' + +/** + * Canonical monetary value used across the Ramp API. The amount is an integer + * in the smallest denomination of the currency (e.g. cents for USD). + */ +export interface RampCurrencyAmount { + amount: number + currency_code: string + minor_unit_conversion_rate: number | null +} + +export interface RampTransaction { + id: string + amount: number + currency_code?: string + merchant_name: string | null + merchant_id: string | null + merchant_category_code?: string + merchant_category_code_description?: string + memo: string | null + state: string + sync_status?: string + user_transaction_time: string + settlement_date?: string + accounting_date?: string | null + card_id?: string + card_holder?: { + user_id?: string + first_name?: string + last_name?: string + department_id?: string + department_name?: string + location_id?: string + location_name?: string + } + receipts?: string[] + entity_id?: string | null + limit_id?: string + spend_program_id?: string + statement_id?: string + trip_id?: string | null + sk_category_id?: number | null + sk_category_name?: string | null + original_transaction_amount?: RampCurrencyAmount | null +} + +export interface RampUser { + id: string + first_name: string + last_name: string + email: string + role: string + status: string + is_manager?: boolean + manager_id?: string | null + department_id?: string | null + location_id?: string | null + entity_id?: string | null + employee_id?: string | null + phone?: string | null +} + +export interface RampCard { + id: string + display_name: string + last_four: string + cardholder_id: string + cardholder_name: string + is_physical: boolean + state: string + expiration: string + created_at: string + entity_id?: string + card_program_id?: string | null +} + +export interface RampLimit { + id: string + display_name: string + state: string + balance?: { + cleared?: RampCurrencyAmount + pending?: RampCurrencyAmount + total?: RampCurrencyAmount + } | null + cards?: Array<{ card_id?: string; expiration?: string; last_four?: string; via?: string }> + users?: Array<{ user_id?: string }> + spend_program_id?: string | null + entity_id?: string | null + is_shareable?: boolean + created_at?: string +} + +export interface RampReimbursement { + id: string + amount: number | null + currency: string + merchant: string | null + memo: string | null + state: string + type: string + direction?: string + user_id: string + user_full_name: string + user_email: string + created_at: string + submitted_at?: string | null + approved_at?: string | null + transaction_date?: string | null + payment_processed_at?: string | null + receipts?: string[] + trip_id?: string | null + entity_id?: string | null + original_reimbursement_amount?: RampCurrencyAmount | null +} + +export interface RampBill { + id: string + invoice_number: string + amount: RampCurrencyAmount + status: string + payment_status?: string + due_at: string + issued_at: string + paid_at?: string + created_at: string + memo?: string + vendor?: { + remote_name?: string + type?: string + vendor_id?: string | null + vendor_owner_id?: string | null + } + entity_id?: string + deep_link_url?: string + accounting_date?: string + invoice_urls?: string[] +} + +export interface RampDepartment { + id: string + name: string +} + +export interface RampVendor { + id: string + name: string + name_legal?: string + state: string + is_active?: boolean + country?: string + description?: string + merchant_id?: string + sk_category_id?: number + sk_category_name?: string + created_at?: string + total_spend_ytd?: RampCurrencyAmount + total_spend_all_time?: RampCurrencyAmount + vendor_owner_id?: string | null +} + +export interface RampBusiness { + id: string + business_name_legal: string + business_name_on_card: string + active: boolean + created_time: string + enforce_sso?: boolean + is_reimbursements_enabled: boolean + is_integrated_with_slack?: boolean + initial_approved_limit?: number + limit_locked?: boolean + phone?: string + website?: string + billing_address?: Record +} + +export interface RampBusinessBalance { + balance_including_pending?: number + balance_including_pending_amount?: RampCurrencyAmount + card_balance_including_pending?: number + card_balance_including_pending_amount?: RampCurrencyAmount + card_balance_excluding_pending?: number + card_balance_excluding_pending_amount?: RampCurrencyAmount + card_limit?: number + card_limit_amount?: RampCurrencyAmount + available_card_limit?: number + available_card_limit_amount?: RampCurrencyAmount + flex_balance?: number + flex_balance_amount?: RampCurrencyAmount + available_flex_limit?: number + available_flex_limit_amount?: RampCurrencyAmount + statement_balance?: number + statement_balance_amount?: RampCurrencyAmount +} + +export interface RampEntity { + id: string + entity_name: string + currency?: string + is_primary?: boolean + location_ids?: string[] + accounts?: unknown[] + payment_accounts?: unknown[] +} + +export interface RampSpendProgram { + id: string + display_name: string + description?: string + icon?: string + is_shareable?: boolean + issue_physical_card_if_needed?: boolean + permitted_spend_types?: { + primary_card_enabled?: boolean + reimbursements_enabled?: boolean + } + restrictions?: Record | null +} + +export interface RampReceipt { + id: string + receipt_url: string + transaction_id?: string + user_id?: string + created_at?: string +} + +interface RampBaseParams { + accessToken?: string +} + +interface RampListParams extends RampBaseParams { + pageSize?: number + start?: string +} + +export interface RampListTransactionsParams extends RampListParams { + userId?: string + cardId?: string + departmentId?: string + merchantId?: string + state?: string + minAmount?: number + maxAmount?: number + fromDate?: string + toDate?: string +} + +export interface RampListTransactionsResponse extends ToolResponse { + output: { + transactions?: RampTransaction[] + nextStart?: string | null + } +} + +export interface RampGetTransactionParams extends RampBaseParams { + transactionId: string +} + +export interface RampGetTransactionResponse extends ToolResponse { + output: { + transaction?: RampTransaction + } +} + +export interface RampListUsersParams extends RampListParams { + email?: string + departmentId?: string +} + +export interface RampListUsersResponse extends ToolResponse { + output: { + users?: RampUser[] + nextStart?: string | null + } +} + +export interface RampGetUserParams extends RampBaseParams { + userId: string +} + +export interface RampGetUserResponse extends ToolResponse { + output: { + user?: RampUser + } +} + +export interface RampListCardsParams extends RampListParams { + userId?: string + displayName?: string +} + +export interface RampListCardsResponse extends ToolResponse { + output: { + cards?: RampCard[] + nextStart?: string | null + } +} + +export interface RampGetCardParams extends RampBaseParams { + cardId: string +} + +export interface RampGetCardResponse extends ToolResponse { + output: { + card?: RampCard + } +} + +export interface RampListLimitsParams extends RampListParams { + userId?: string + cardId?: string +} + +export interface RampListLimitsResponse extends ToolResponse { + output: { + limits?: RampLimit[] + nextStart?: string | null + } +} + +export interface RampListReimbursementsParams extends RampListParams { + userId?: string + fromDate?: string + toDate?: string +} + +export interface RampListReimbursementsResponse extends ToolResponse { + output: { + reimbursements?: RampReimbursement[] + nextStart?: string | null + } +} + +export interface RampGetReimbursementParams extends RampBaseParams { + reimbursementId: string +} + +export interface RampGetReimbursementResponse extends ToolResponse { + output: { + reimbursement?: RampReimbursement + } +} + +export interface RampListBillsParams extends RampListParams { + vendorId?: string +} + +export interface RampListBillsResponse extends ToolResponse { + output: { + bills?: RampBill[] + nextStart?: string | null + } +} + +export interface RampGetBillParams extends RampBaseParams { + billId: string +} + +export interface RampGetBillResponse extends ToolResponse { + output: { + bill?: RampBill + } +} + +export interface RampListDepartmentsParams extends RampListParams {} + +export interface RampListDepartmentsResponse extends ToolResponse { + output: { + departments?: RampDepartment[] + nextStart?: string | null + } +} + +export interface RampListVendorsParams extends RampListParams { + vendorName?: string +} + +export interface RampListVendorsResponse extends ToolResponse { + output: { + vendors?: RampVendor[] + nextStart?: string | null + } +} + +export interface RampListReceiptsParams extends RampListParams { + transactionId?: string + fromDate?: string + toDate?: string +} + +export interface RampListReceiptsResponse extends ToolResponse { + output: { + receipts?: RampReceipt[] + nextStart?: string | null + } +} + +export interface RampGetReceiptParams extends RampBaseParams { + receiptId: string +} + +export interface RampGetReceiptResponse extends ToolResponse { + output: { + receipt?: RampReceipt + } +} + +export interface RampGetBusinessParams extends RampBaseParams {} + +export interface RampGetBusinessResponse extends ToolResponse { + output: { + business?: RampBusiness + } +} + +export interface RampGetBusinessBalanceParams extends RampBaseParams {} + +export interface RampGetBusinessBalanceResponse extends ToolResponse { + output: { + balance?: RampBusinessBalance + } +} + +export interface RampGetLimitParams extends RampBaseParams { + limitId: string +} + +export interface RampGetLimitResponse extends ToolResponse { + output: { + limit?: RampLimit + } +} + +export interface RampGetDepartmentParams extends RampBaseParams { + departmentId: string +} + +export interface RampGetDepartmentResponse extends ToolResponse { + output: { + department?: RampDepartment + } +} + +export interface RampCreateDepartmentParams extends RampBaseParams { + departmentName: string +} + +export interface RampCreateDepartmentResponse extends ToolResponse { + output: { + department?: RampDepartment + } +} + +export interface RampGetVendorParams extends RampBaseParams { + vendorId: string +} + +export interface RampGetVendorResponse extends ToolResponse { + output: { + vendor?: RampVendor + } +} + +export interface RampListEntitiesParams extends RampListParams { + entityName?: string +} + +export interface RampListEntitiesResponse extends ToolResponse { + output: { + entities?: RampEntity[] + nextStart?: string | null + } +} + +export interface RampListSpendProgramsParams extends RampListParams {} + +export interface RampListSpendProgramsResponse extends ToolResponse { + output: { + spendPrograms?: RampSpendProgram[] + nextStart?: string | null + } +} + +export interface RampGetSpendProgramParams extends RampBaseParams { + spendProgramId: string +} + +export interface RampGetSpendProgramResponse extends ToolResponse { + output: { + spendProgram?: RampSpendProgram + } +} + +export interface RampUploadReceiptParams extends RampBaseParams { + userId: string + transactionId?: string + file?: UserFileLike +} + +export interface RampUploadReceiptResponse extends ToolResponse { + output: { + receiptId?: string + } +} + +export type RampResponse = + | RampListTransactionsResponse + | RampGetTransactionResponse + | RampListUsersResponse + | RampGetUserResponse + | RampListCardsResponse + | RampGetCardResponse + | RampListLimitsResponse + | RampGetLimitResponse + | RampListReimbursementsResponse + | RampGetReimbursementResponse + | RampListBillsResponse + | RampGetBillResponse + | RampListDepartmentsResponse + | RampGetDepartmentResponse + | RampCreateDepartmentResponse + | RampListVendorsResponse + | RampGetVendorResponse + | RampListEntitiesResponse + | RampListSpendProgramsResponse + | RampGetSpendProgramResponse + | RampGetBusinessResponse + | RampGetBusinessBalanceResponse + | RampListReceiptsResponse + | RampGetReceiptResponse + | RampUploadReceiptResponse diff --git a/apps/sim/tools/ramp/upload_receipt.ts b/apps/sim/tools/ramp/upload_receipt.ts new file mode 100644 index 0000000000..f20d50d227 --- /dev/null +++ b/apps/sim/tools/ramp/upload_receipt.ts @@ -0,0 +1,81 @@ +import type { RampUploadReceiptParams, RampUploadReceiptResponse } from '@/tools/ramp/types' +import type { ToolConfig } from '@/tools/types' + +export const rampUploadReceiptTool: ToolConfig = + { + id: 'ramp_upload_receipt', + name: 'Ramp Upload Receipt', + description: + 'Upload a receipt image to Ramp and optionally attach it to a transaction. When no transaction is provided, Ramp matches the receipt to the most relevant transaction automatically.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'ramp', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for the Ramp API', + }, + userId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the Ramp user to associate with the receipt', + }, + transactionId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'ID of the transaction to attach the receipt to', + }, + file: { + type: 'file', + required: true, + visibility: 'user-or-llm', + description: 'The receipt image or PDF to upload', + }, + }, + + request: { + url: '/api/tools/ramp/upload-receipt', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => ({ + accessToken: params.accessToken, + userId: params.userId, + transactionId: params.transactionId, + file: params.file, + }), + }, + + transformResponse: async (response): Promise => { + const data = await response.json() + + if (!response.ok || !data.success) { + return { + success: false, + error: data.error || 'Failed to upload receipt to Ramp', + output: {}, + } + } + + return { + success: true, + output: data.output, + } + }, + + outputs: { + receiptId: { + type: 'string', + description: 'Unique identifier of the uploaded receipt', + }, + }, + } diff --git a/apps/sim/tools/ramp/utils.ts b/apps/sim/tools/ramp/utils.ts new file mode 100644 index 0000000000..e3d5bb4c39 --- /dev/null +++ b/apps/sim/tools/ramp/utils.ts @@ -0,0 +1,66 @@ +/** + * Base URL for the Ramp Developer API (v1). + */ +export const RAMP_API_BASE_URL = 'https://api.ramp.com/developer/v1' + +/** + * Builds the standard authorization headers for Ramp API requests. + * Throws when the OAuth access token has not been resolved yet. + */ +export function buildRampHeaders(params: { accessToken?: string }): Record { + if (!params.accessToken) { + throw new Error('Missing access token for Ramp API request') + } + return { + Authorization: `Bearer ${params.accessToken}`, + Accept: 'application/json', + } +} + +/** + * Builds a Ramp API URL from a path and a map of query parameters, + * omitting entries whose value is undefined, null, or an empty string. + */ +export function buildRampUrl( + path: string, + query: Record = {} +): string { + const searchParams = new URLSearchParams() + for (const [key, value] of Object.entries(query)) { + if (value === undefined || value === null || value === '') continue + searchParams.set(key, String(value)) + } + const queryString = searchParams.toString() + return `${RAMP_API_BASE_URL}${path}${queryString ? `?${queryString}` : ''}` +} + +/** + * Extracts a human-readable error message from a Ramp API error body. + * Ramp returns structured errors under `error_v2` with a legacy top-level + * `message` fallback. + */ +export function extractRampError(data: unknown, fallback: string): string { + if (data && typeof data === 'object') { + const body = data as { + error_v2?: { message?: string; error_code?: string } + message?: string + error?: string + } + return body.error_v2?.message || body.message || body.error || fallback + } + return fallback +} + +/** + * Extracts the `start` cursor from a Ramp paginated response's `page.next` + * URL so it can be passed back as the `start` parameter of the next request. + * Returns null when there is no next page. + */ +export function extractNextStart(next: string | null | undefined): string | null { + if (!next) return null + try { + return new URL(next).searchParams.get('start') + } catch { + return null + } +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 2923847b02..66280f8dc2 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -2379,6 +2379,33 @@ import { railwayUpdateProjectTool, railwayUpsertVariableTool, } from '@/tools/railway' +import { + rampCreateDepartmentTool, + rampGetBillTool, + rampGetBusinessBalanceTool, + rampGetBusinessTool, + rampGetCardTool, + rampGetDepartmentTool, + rampGetLimitTool, + rampGetReceiptTool, + rampGetReimbursementTool, + rampGetSpendProgramTool, + rampGetTransactionTool, + rampGetUserTool, + rampGetVendorTool, + rampListBillsTool, + rampListCardsTool, + rampListDepartmentsTool, + rampListEntitiesTool, + rampListLimitsTool, + rampListReceiptsTool, + rampListReimbursementsTool, + rampListSpendProgramsTool, + rampListTransactionsTool, + rampListUsersTool, + rampListVendorsTool, + rampUploadReceiptTool, +} from '@/tools/ramp' import { rb2bCreditCheckTool, rb2bEmailToActivityTool, @@ -6161,6 +6188,31 @@ export const tools: Record = { railway_transfer_project: railwayTransferProjectTool, railway_update_project: railwayUpdateProjectTool, railway_upsert_variable: railwayUpsertVariableTool, + ramp_create_department: rampCreateDepartmentTool, + ramp_get_bill: rampGetBillTool, + ramp_get_business: rampGetBusinessTool, + ramp_get_business_balance: rampGetBusinessBalanceTool, + ramp_get_card: rampGetCardTool, + ramp_get_department: rampGetDepartmentTool, + ramp_get_limit: rampGetLimitTool, + ramp_get_receipt: rampGetReceiptTool, + ramp_get_reimbursement: rampGetReimbursementTool, + ramp_get_spend_program: rampGetSpendProgramTool, + ramp_get_transaction: rampGetTransactionTool, + ramp_get_user: rampGetUserTool, + ramp_get_vendor: rampGetVendorTool, + ramp_list_bills: rampListBillsTool, + ramp_list_cards: rampListCardsTool, + ramp_list_departments: rampListDepartmentsTool, + ramp_list_entities: rampListEntitiesTool, + ramp_list_limits: rampListLimitsTool, + ramp_list_receipts: rampListReceiptsTool, + ramp_list_reimbursements: rampListReimbursementsTool, + ramp_list_spend_programs: rampListSpendProgramsTool, + ramp_list_transactions: rampListTransactionsTool, + ramp_list_users: rampListUsersTool, + ramp_list_vendors: rampListVendorsTool, + ramp_upload_receipt: rampUploadReceiptTool, hunter_discover: hunterDiscoverTool, hunter_domain_search: hunterDomainSearchTool, hunter_email_finder: hunterEmailFinderTool,