Skills

Cards and Spend

Spend analysis

ramp-spend-analysis

CLIMCP

Analyze spend by vendor, category, or team over a date range. Pulls card transactions and bill payments, aggregates totals, and produces summary tables. Use when: 'how much did we spend on', 'vendor spend', 'SaaS review', 'spend report', 'inference costs', 'total spend', 'spend by vendor', 'spend analysis', 'pull transactions for', 'cost breakdown'. Do NOT use for: approving transactions (use ramp-approval-dashboard), uploading receipts (use ramp-complete-expenses), or verifying a single bill payment (use ramp-payment-lookup).

Skill definition

SKILL.md
---
name: ramp-spend-analysis
area: Cards and Spend
supported_surfaces: [cli, mcp]
description: |-
  Analyze spend by vendor, category, or team over a date range. Pulls card transactions
  and bill payments, aggregates totals, and produces summary tables.
  Use when: 'how much did we spend on', 'vendor spend', 'SaaS review', 'spend report',
  'inference costs', 'total spend', 'spend by vendor', 'spend analysis',
  'pull transactions for', 'cost breakdown'.
  Do NOT use for: approving transactions (use ramp-approval-dashboard), uploading receipts
  (use ramp-complete-expenses), or verifying a single bill payment (use ramp-payment-lookup).
---

## Non-Negotiables

- **Pass `--rationale` on every command** — it is a required field on these agent-tools (a non-empty string, max 1024 chars). With `--json`, supply it as a `"rationale"` key in the body. Omitting it returns `HTTP 422 (DEVELOPER_INVALID_SCHEMA)`, in both agent and human modes.
- Always query both **transactions** and **bills** when investigating complete vendor spend. Card charges and bill payments are separate resources — there is no unified spend endpoint.
- Never treat a search result's bill `amount` as paid-in-period spend. It is the full invoice amount, and a payment-date match may represent only one partial payment. A complete paid total requires payment-allocation amounts and dates from another source.
- Pass `--agent` for machine-readable JSON output on all commands.
- Handle amount format differences: transactions use strings (`"$1,048.25"`, `"-$259.49"`), bills use numbers (`15000`), PO amounts use numbers. Reimbursement amounts are in dollars.
- Paginate until `next_page_cursor` is null — a single page may not return everything.
- Flag vendor name variants explicitly (e.g., "Delta Air Lines" vs "Delta Airlines") — the API does not normalize.
- Negative transaction amounts are refunds. Include them in totals but call them out.

## Workflow

### Step 1: Pull card transactions

For each vendor (or all vendors if doing a broad analysis):

```bash
ramp transactions list \
  --transactions_to_retrieve all_transactions_across_entire_business \
  --reason_memo_merchant_or_user_name_text_search "<vendor>" \
  --from_date <YYYY-MM-DD> \
  --to_date <YYYY-MM-DD> \
  --include_count \
  --page_size 200 \
  --agent --rationale "List the user's transactions"
```

If `next_page_cursor` is not null, paginate:

```bash
ramp transactions list \
  --transactions_to_retrieve all_transactions_across_entire_business \
  --reason_memo_merchant_or_user_name_text_search "<vendor>" \
  --from_date <YYYY-MM-DD> \
  --to_date <YYYY-MM-DD> \
  --page_size 200 \
  --next_page_cursor "<cursor>" \
  --agent --rationale "List the user's transactions"
```

**For broad analysis (all vendors):** Omit `--reason_memo_merchant_or_user_name_text_search` to pull all transactions, then group client-side.

### Step 2: Pull bill payments

For a specific vendor:

```bash
ramp bills search --query "<vendor>" --include_paid \
  --from_payment_date <YYYY-MM-DD> --to_payment_date <YYYY-MM-DD> \
  --limit 50 --agent --rationale "Search bills paid during the user's requested period"
```

For broad analysis (all vendors):

```bash
ramp bills search --include_paid \
  --from_payment_date <YYYY-MM-DD> --to_payment_date <YYYY-MM-DD> \
  --limit 50 --agent --rationale "Search all bills paid during the user's requested period"
```

Repeat either with `--page_cursor` if `next_page_cursor` is not null, preserving the same query and date bounds on every page.

**Note:** Use `from_payment_date` and `to_payment_date` to find bills with payment activity in the period, then use each result's `payment_date` when presenting or validating the period. Do not substitute `due_date`: it describes when payment was due, not when spend was paid. A matched bill may be partially paid, while its `amount` is the full invoice amount. Do not add that amount to actual spend without payment-allocation data. Bills with `payment_status: "OPEN"` are unpaid — report them separately as commitments.

### Step 3: Parse and aggregate

#### Transaction amounts (strings → numbers)

```bash
# Sum a single vendor's transactions
... | jq '[.data[0].transactions[].amount | gsub(","; "") | if startswith("-$") then ltrimstr("-$") | tonumber | (. * -1) elif startswith("$") then ltrimstr("$") | tonumber else tonumber end] | add'
```

#### Bill amounts are invoice amounts, not paid amounts

Use bill search to identify payment activity and relevant bill IDs. Do not sum
`BillInfo.amount` as paid-in-period spend: payment-date filtering can match one
installment of a partially paid bill while `amount` remains the full invoice
amount. Only combine bills into an actual-spend total when payment-allocation
amounts and dates are available; otherwise present the card subtotal and matched
bills separately, and label the complete paid total unavailable.

#### Multi-vendor table with pagination

Fetch all pages first, then aggregate. Write only the minimum fields needed into
a **fresh per-run directory** (`mktemp -d`) — never a fixed path or glob shared
across runs, or stale pages from a previous or concurrent analysis get silently
included in the totals. These files can contain sensitive spend data: do not
commit or upload them, redact values before any user-approved sharing, and
delete the directory as soon as the analysis is complete. The loop continues
until `next_page_cursor` is null:

```bash
run_dir=$(mktemp -d /tmp/spend_analysis.XXXXXX)

# Page 1
ramp transactions list \
  --transactions_to_retrieve all_transactions_across_entire_business \
  --from_date 2026-01-01 --page_size 200 --include_count --agent \
  --rationale "List the user's transactions" \
  > "$run_dir/txns_page1.json"

# Page 2+ (repeat until next_page_cursor is null)
ramp transactions list \
  --transactions_to_retrieve all_transactions_across_entire_business \
  --from_date 2026-01-01 --page_size 200 --include_count --agent \
  --next_page_cursor "<cursor from previous page>" \
  --rationale "List the user's transactions" \
  > "$run_dir/txns_page2.json"
```

Then merge all pages from this run's directory and build the vendor table:

```bash
jq -s -r '
  [.[].data[0].transactions[] |
    {merchant: .merchant_name,
     amt: (.amount | gsub(","; "") |
       if startswith("-$") then ltrimstr("-$") | tonumber | (. * -1)
       elif startswith("$") then ltrimstr("$") | tonumber
       else tonumber end)}]
  | group_by(.merchant)
  | map({vendor: .[0].merchant, total: (map(.amt) | add), count: length})
  | sort_by(-.total)
  | .[] | "\(.vendor)\t$\(.total)\t(\(.count) txns)"
' "$run_dir"/txns_page*.json
```

Clean up with `rm -rf "$run_dir"` when the analysis is done.

### Step 4: Present results

Format as a clear table:

```
Vendor Spend: 2026-01-01 to 2026-04-01

Vendor              Card Spend    Bills Matched    Complete Paid Total
─────────────────────────────────────────────────────────────────────
Figma               $24.00        0                $24.00
Delta Airlines      $3,663.73     0                $3,663.73
AWS                 $12,450.00    2                Requires payment-allocation data

Confirmed card subtotal: $16,137.73
```

Call out:
- **Vendor name variants** found (e.g., "Delta Air Lines" + "Delta Airlines")
- **Refunds** included in the totals
- **Bills vs transactions** breakdown if both exist for a vendor; do not add full invoice amounts to paid spend
- **Pagination** — whether all results were captured or if there are more pages

## Fields Available

### From `transactions list`

| Field | Description |
|---|---|
| `transaction_uuid` | Transaction UUID (use for deduplication) |
| `merchant_name` | Merchant name (not normalized) |
| `amount` | Formatted string (`"$8.00"`, `"-$259.49"`, `"$1,048.25"`) |
| `transaction_time` | ISO 8601 timestamp |
| `spent_by_user` | Employee name |
| `merchant_category` | Category (e.g., "SaaS / Software", "Airlines") |
| `reason_or_justification` | Memo / reason |
| `spend_allocation_name` | Fund / budget name |
| `transaction_link` | Direct link to transaction in Ramp UI |

### From `bills search`

| Field | Description |
|---|---|
| `id` | Bill UUID |
| `vendor_name` | Payee name |
| `amount` | Full invoice amount in dollars; not necessarily the amount paid in the requested period |
| `invoice_number` | Vendor invoice number |
| `payment_date` | Displayed payment date, typically the scheduled or sent date |
| `payment_status` | Payment state |
| `memo` | Bill description |

## Multi-Vendor Queries

For SaaS reviews or inference spend monitoring, run vendors in parallel:

```bash
# Run these concurrently
ramp transactions list --transactions_to_retrieve all_transactions_across_entire_business \
  --reason_memo_merchant_or_user_name_text_search "Figma" --from_date 2026-01-01 --include_count --agent --rationale "List the user's transactions"

ramp transactions list --transactions_to_retrieve all_transactions_across_entire_business \
  --reason_memo_merchant_or_user_name_text_search "Anthropic" --from_date 2026-01-01 --include_count --agent --rationale "List the user's transactions"

ramp transactions list --transactions_to_retrieve all_transactions_across_entire_business \
  --reason_memo_merchant_or_user_name_text_search "OpenAI" --from_date 2026-01-01 --include_count --agent --rationale "List the user's transactions"
```

**Tip:** For vendors with known name variants, run multiple searches and deduplicate by `transaction_uuid`:
- "AWS" and "Amazon Web Services"
- "Delta Air Lines" and "Delta Airlines"

## Vendor Name Variant Detection

The API does not normalize merchant names. Common variants to watch for:

| Vendor | Possible variants |
|---|---|
| AWS | "AWS", "Amazon Web Services", "Amazon.com Services" |
| Delta | "Delta Air Lines", "Delta Airlines" |
| Google | "Google Cloud", "Google", "GCP" |

When running broad analysis (all transactions), group results and flag merchants with similar names. The agent should suggest merges.

## Example Session

```
User: How much have we spent on Figma this year?

Agent: Let me pull both card transactions and bills.
> ramp transactions list --transactions_to_retrieve all_transactions_across_entire_business \
>   --reason_memo_merchant_or_user_name_text_search "Figma" \
>   --from_date 2026-01-01 --to_date 2026-04-01 --include_count --agent \
>   --rationale "List the user's transactions"
> ramp bills search --query "Figma" --include_paid --from_payment_date 2026-01-01 \
>   --to_payment_date 2026-04-01 --limit 50 --agent \
>   --rationale "Search Figma bills paid during the user's requested period"

Figma spend (2026-01-01 to 2026-04-01):

  Card transactions: 3 totaling $24.00
    $8.00  Michael Scott  2026-03-17  "Trial spend for one Sales user"
    $8.00  Michael Scott  2026-03-17  "Trial spend for one Sales user"
    $8.00  Michael Scott  2026-03-17  "Trial spend for one Sales user"

  Bills: 0

  Total: $24.00

Note: There's also an open PO for Figma Enterprise ($150,042.75, PO #9880)
if you want to check commitment vs actual spend.

User: Give me a full SaaS vendor breakdown

Agent: Pulling all card transactions and bills with payment activity, then matching them by vendor...
> ramp transactions list --transactions_to_retrieve all_transactions_across_entire_business \
>   --from_date 2026-01-01 --to_date 2026-04-01 --page_size 200 --include_count \
>   --agent --rationale "List the user's transactions"
> ramp bills search --include_paid --from_payment_date 2026-01-01 \
>   --to_payment_date 2026-04-01 --limit 50 --agent \
>   --rationale "Search all bills paid during the user's requested period"
> # Paginate both searches to completion, then merge by vendor.

SaaS Vendor Spend Review (2026-01-01 to 2026-04-01):

Vendor              Card Spend    Bills Matched    Complete Paid Total
─────────────────────────────────────────────────────────────────────
Brown Group         $50,000.00    1                Requires payment-allocation data
Cochran Ltd         $21,500.00    0                $21,500.00
Goody               $9,119.13     2                Requires payment-allocation data
Morris-Allen        $8,900.00     0                $8,900.00
Figma               $24.00        1                Requires payment-allocation data

⚠ Vendor name variants detected:
  "Delta Air Lines" (2 txns) + "Delta Airlines" (5 txns) — likely same vendor

Bill matches were filtered by `payment_date`, but their full invoice amounts were
not added to spend. Payment-allocation data is required to complete those totals.
```

## When NOT to Use

- **Verifying a single payment** — use `ramp-payment-lookup`
- **Approving transactions** — use `ramp-approval-dashboard`
- **Receipt or memo cleanup** — use `ramp-complete-expenses`
- **Detailed PO status** — run `ramp purchase_orders get` directly

## Gotchas

| Issue | Fix |
|---|---|
| Transaction amounts are strings (`"$1,048.25"`) | Strip `$` and `,` before summing. Handle `-$` prefix for refunds. |
| Bill amounts are numeric (dollars) | They are full invoice amounts. Do not sum them as paid-in-period spend without payment-allocation data. |
| `--transactions_to_retrieve` is required | Always include it. Use `all_transactions_across_entire_business` for company-wide analysis. |
| Search text must be ≥3 characters | "AI" won't work — use full vendor name |
| Paid-in-period bill activity | Pass `--from_payment_date` and `--to_payment_date` to `bills search`, and use the returned `payment_date`. Do not fall back to `due_date` or treat full invoice `amount` as the paid allocation. |
| Vendor name variants not normalized | Run multiple searches for known variants. Dedupe by `transaction_uuid`. |
| Pagination ceiling | `--page_size 200` is accepted. Still check `next_page_cursor`. |
| No unified spend endpoint | Query transactions and bills separately, then merge the results. |
| `--include_count` only on transactions | Bills search returns `total_found` automatically. |
| Something broken? | With the user's consent, run `ramp feedback "<user-approved message>"`. This sends only that message to Ramp support; omit secrets and diagnostic artifacts. |